Looks like no one’s replied in a while. To start the conversation again, simply ask a new question.

launchd ignores RunAtLoad=false

After having successfully set up a backup job with rsync that is launched with a launchd script, there is only one thing that remains:


launchd runs my script every time it is loaded, i.e. at every startup, instead of just at the specified day and time. I know that the RunAtLoad key default is false, but I still added it to thest whether it helped, but it has not.


What could be the problem?


Here is my launchd plist file (which I located in /Library/LaunchAgents/)


<?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>Label</key>
    <string>org.jenal.docs_backup</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/marcus/bin/enterprise_docs_rsync.sh</string>
    </array>
          <key>RunAtLoad</key>
                    <false/>
    <key>StartCalendarInterval</key>
              <dict>
                  <key>Hour</key>
                  <integer>11</integer>
                  <key>Minute</key>
                  <integer>30</integer>
                  <key>Weekday</key>
                  <integer>5</integer>
              </dict>
          <key>KeepAlive</key>
          <dict>
              <key>SuccessfulExit</key>
              <false/>
          </dict>
          <key>StandardOutPath</key>
          <string>/Users/marcus/tmp/launchd_pix.out</string>
          <key>StandardErrorPath</key>
          <string>/Users/marcus/tmp/launchd_pix.err</string>
</dict>
</plist>


Appreciate any help with that!

iMac (21.5-inch Mid 2011), OS X Mountain Lion (10.8.2)

Posted on Jan 28, 2013 11:46 AM

Reply
19 replies

Jan 28, 2013 12:07 PM in response to twtwtw

Thanks, twtwtw for your reply. The intention of the KeepAlive key is to keep the job running until the backup is done successfully (which also does not work but I guess that would be another disussion thread). I have tried to take it out but that did not help. The script acutally really does what it should, it also runs at the specified times. It just runs ALSO at the time when the launchd plist is loaded, i.e. daily when I start up my Mac, which is weired and unnesseccary.

Jan 28, 2013 12:18 PM in response to wondermac

Well, that's not how the KeepAlive key works. what you're basically doing with it is forcing the shell script to relaunch if it quits with an error.


I think what you really want is to get rid of the KeepAlive key and add an AbandonProcessGroup key set to true. I'm thinking this is what's happening:


  1. launchd runs the job and launches the shell script
  2. the shell script launches rsync and quits
  3. launchd sees the shell script quit and kills all its child process (e.g. rsync)


The AbandonProcessGroup key tells launchd not to kill child processes even if the mother script quits and the job ends.

Jan 28, 2013 12:34 PM in response to twtwtw

You are right. The KeepAlive key was apparently the problem. But I don't really get it.

twtwtw wrote:


what you're basically doing with it is forcing the shell script to relaunch if it quits with an error.


Isn't that what I want? I want the shell script to be run again, i.e. rsync would attempt again to finish the backup, until rsync quits without an error, i.e. the backup is done.


But actually, this is not what is happening. I sometimes do get an error from the rsync script but the script is not run again. Interestingly, the error only shows up in the rsync log file, but not in the error file I defined in launchd:


          <key>StandardErrorPath</key>
<string>/Users/marcus/tmp/launchd_pix.err</string>


But again back to the matter at hand. I cannot confirm your guess that


  1. launchd runs the job and launches the shell script
  2. the shell script launches rsync and quits
  3. launchd sees the shell script quit and kills all its child process (e.g. rsync)


Actually, rsync does most of the times complete the backup successfully and is not killed by launchd.


But I don't really understand what the KeepAliveKey has to do with the fact that the script was run at every start of the Mac, but also at the designeted schedule.


And how do I now make sure that the script is run again until rsync exits without error?

Jan 28, 2013 12:54 PM in response to wondermac

I was being formal with Program. If the Program key is missing, then the first element of the array of strings from ProgramArguments key is used. This models the positional elements in the execvp(3) system call.


However, I have noticed a pattern in the OS X LaunchAgents. It is very common to see the Program key/string pair immediately followed by the RunAtLoad key syntax -- even if it does not appear at the beginning of the .plist file. However, if you look at com.apple.Finder.plist, the very first XML statement begins with RunAtLoad and the Program keyword comes later.

Jan 28, 2013 2:00 PM in response to wondermac

wondermac wrote:


You are right. The KeepAlive key was apparently the problem. But I don't really get it.

twtwtw wrote:


what you're basically doing with it is forcing the shell script to relaunch if it quits with an error.


Isn't that what I want? I want the shell script to be run again, i.e. rsync would attempt again to finish the backup, until rsync quits without an error, i.e. the backup is done.

You haven't included your shell script, so I can't be specific, but the KeepAlive key is intended for processes that run continuously. The idea is that you start the process running, then launchd monitors it and restarts it under various conditions. I suspect, however, that you're running a one-pass script; something that launches rsync and then ends. so let me expand the flow I gave above:


  1. at the correct time, launchd runs the job, which in turn launches the shell script
  2. the shell script launches rsync and then ends
  3. rsync starts trying to do a backup
  4. launchd sees that main shell script ended and kills all its child processes
  5. rsync is killed ungracefully by the system
  6. because it had to kill child processes, launchd marks the job as having an unsuccessful exit
  7. launchd launches the shell script again immediately, because of the KeepAlive options you've chosen.
  8. repeat steps 2 through 7 every ten seconds or so until the machine is restarted (or rsync quits gracefully)
  9. machine restarts, but launchd still remembers if the last run of that process ended unsuccessfully and tries to start it again.


Isn't launchd fun? 😀


But actually, this is not what is happening. I sometimes do get an error from the rsync script but the script is not run again. Interestingly, the error only shows up in the rsync log file, but not in the error file I defined in launchd:


A few things going on here. first, rsync has it's own error system, and (if I remember correctly) doesn't pass errors to standard error. it just writes them to the log. Second, rsync is not the process the launchd job started, rsync is a child of that process (the shell script) running independently. Third, rsync is a relatively smart utility, so it may very well be progressing through the backup incrementally across multiple quits. Whenever rsync quits gracefully (finishes a backup or quits itself from an internal error), launchd marks the job as a successful exit and does not try to restart it.


At any rate, to get the effect you want, first edit your rsync command to send output and error messages where you want them to go - you'll have to convince rsync to do it, launchd won't catch it. Then use the following plist:


<?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>Label</key>

<string>org.jenal.docs_backup</string>

<key>ProgramArguments</key>

<array>

<string>/Users/marcus/bin/enterprise_docs_rsync.sh</string>

</array>

<key>AbandonProcessGroup</key>

<true/>

<key>StartCalendarInterval</key>

<dict>

<key>Hour</key>

<integer>11</integer>

<key>Minute</key>

<integer>30</integer>

<key>Weekday</key>

<integer>5</integer>

</dict>

<key>StandardOutPath</key>

<string>/Users/marcus/tmp/launchd_pix.out</string>

<key>StandardErrorPath</key>

<string>/Users/marcus/tmp/launchd_pix.err</string>

</dict>

</plist>


I wouldn't worry about trying to monitor rsync in action. it's written to be robust; you should let it do its thing.

Jan 28, 2013 2:39 PM in response to twtwtw

Hey twtwtw. Thanks so much for your effort. I have seen many discussions in the forum talking about the problem of launchd restarting processes every 10 seconds or so. In that case, your analysis might be correct. In my case, however, this is not happening. So I cannot confirm your flow, at least not for my case

twtwtw wrote:




  1. at the correct time, launchd runs the job, which in turn launches the shell script
  2. the shell script launches rsync and then ends
  3. rsync starts trying to do a backup
  4. launchd sees that main shell script ended and kills all its child processes
  5. rsync is killed ungracefully by the system
  6. because it had to kill child processes, launchd marks the job as having an unsuccessful exit
  7. launchd launches the shell script again immediately, because of the KeepAlive options you've chosen.
  8. repeat steps 2 through 7 every ten seconds or so until the machine is restarted (or rsync quits gracefully)
  9. machine restarts, but launchd still remembers if the last run of that process ended unsuccessfully and tries to start it again.


In my case, rsync runs, either sucessuflly or with a connection error (usually a time-out problem with the server) and then quits gracefully. It is not killed by launchd. BUT it is also not restarted by launchd if an error occurs.


That means, I guess:

  1. at startup or at the correct time, launchd runs the job, which in turn launches the shell script
  2. the shell script launches rsync and then ends
  3. rsync starts trying to do a backup
  4. launchd sees that main shell script ended and hence the keep alive value is fulfilled and it does not restart the script, even if rsync quits with an error


As you suggested, I have a relatively easy script that just runs a backup in rsync:


#!/bin/sh


SRC="/Volumes/Data/Pictures"

DEST="rsync@cloud9.local:/volume1/Backups/"

OPTIONS="-e ssh -E -azvhHS --delete --numeric-ids --log-file=/Users/marcus/tmp/rlog_pix"

EXCLUSIONS="--exclude .DS_Store --exclude */.Trash --exclude .Spotlight-*/ --exclude */$RECYCLE.BIN"

/usr/local/bin/rsync $OPTIONS $SRC $DEST

exit 0


So, as the script is not run continuously when the Mac is on but just once at login (plus at the scheduled times), I still don't understand why KeepAlive was the reason that it run at startup.


Anyway, my script now looks like this:

<?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>Label</key>

<string>org.jenal.picture_backup</string>

<key>ProgramArguments</key>

<array>

<string>/Users/marcus/bin/enterprise_pictures_rsync.sh</string>

</array>

<key>StartCalendarInterval</key>

<dict>

<key>Hour</key>

<integer>11</integer>

<key>Minute</key>

<integer>30</integer>

<key>Weekday</key>

<integer>1</integer>

</dict>

<key>StandardOutPath</key>

<string>/Users/marcus/tmp/launchd_pix.out</string>

<key>StandardErrorPath</key>

<string>/Users/marcus/tmp/launchd_pix.err</string>

</dict>

</plist>


That basically does the trick for me. The only thing that it is not yet working is that launchd restarts the script if rsync quits with an error. I haven't figured out how to do that. I guess the script would have to wait for rsync to complete and then either quit if there was no error or report an error to launchd if there was so it could be restarted. Or the restart if error could also be done in the script itself, I guess.

Jan 28, 2013 5:11 PM in response to wondermac

well, I figured out why KeepAlive starts the process at login. From the launchd.plist man page (highlight mine):


SuccessfulExit <boolean>

If true, the job will be restarted as long as the program exits and

with an exit status of zero. If false, the job will be restarted

in the inverse condition. This key implies that "RunAtLoad" is set

to true, since the job needs to run at least once before we can get

an exit status.


the SuccessfulExit key acts as an implicit RunAtLoad command. Now, there's an undocumented key called AfterInitialDemand that supposedly makes launchd wait for the process to be called before invoking KeepAlive. That would be what you need if it does what it doesn't anywhere say that it's supposed to do. also, looking at your shell script, it would be better in your case not to use a shell script at all, but to call rsync straight from launchd. So try the following and see if it gets you where you want (I didn't include the exclusions because they're not in the actual rsync command):


<?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>Label</key>

<string>org.jenal.picture_backup</string>

<key>Program</key>

<string>/usr/local/bin/rsync</string>

<key>ProgramArguments</key>

<array>

<string>rsync</string>

<string>-e</string>

<string>ssh</string>

<string>-E</string>

<string>-azvhHS</string>

<string>--delete</string>

<string>--numeric-ids</string>

<string>--log-file=/Users/marcus/tmp/rlog_pix</string>

<string>/Volumes/Data/Pictures</string>

<string>rsync@cloud9.local:/volume1/Backups/</string>

</array>

<key>StartCalendarInterval</key>

<dict>

<key>Hour</key>

<integer>11</integer>

<key>Minute</key>

<integer>30</integer>

<key>Weekday</key>

<integer>1</integer>

</dict>

<key>KeepAlive</key>

<dict>

<key>SuccessfulExit</key>

<false/>

<key>AfterInitialDemand</key>

<true/>

</dict>

<key>StandardOutPath</key>

<string>/Users/marcus/tmp/launchd_pix.out</string>

<key>StandardErrorPath</key>

<string>/Users/marcus/tmp/launchd_pix.err</string>

</dict>

</plist>

Feb 1, 2013 11:53 AM in response to twtwtw

twtwtw, I tested your script and it works like a charm. Thanks. I included the exclusions (through --exclude-from and linking that to a text file with the exclusions).


It does not start at every start and it does keep the script alive when rsync quits with an error.


Here is the final launchd code:


<?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>Label</key>

<string>org.jenal.picture_backup</string>

<key>Program</key>

<string>/usr/local/bin/rsync</string>

<key>ProgramArguments</key>

<array>

<string>rsync</string>

<string>-e</string>

<string>ssh</string>

<string>-E</string>

<string>-azvhHS</string>

<string>--delete</string>

<string>--numeric-ids</string>

<string>--exclude-from</string>

<string>/Users/marcus/tmp/exclude.txt</string>

<string>--log-file=/Users/marcus/tmp/rlog_pix</string>

<string>/Volumes/Data/Pictures</string>

<string>rsync@cloud9.local:/volume1/Backups/</string>

</array>

<key>StartCalendarInterval</key>

<dict>

<key>Hour</key>

<integer>11</integer>

<key>Minute</key>

<integer>30</integer>

<key>Weekday</key>

<integer>1</integer>

</dict>

<key>KeepAlive</key>

<dict>

<key>SuccessfulExit</key>

<false/>

<key>AfterInitialDemand</key>

<true/>

</dict>

<key>StandardOutPath</key>

<string>/Users/marcus/tmp/launchd_pix.out</string>

<key>StandardErrorPath</key>

<string>/Users/marcus/tmp/launchd_pix.err</string>

</dict>

</plist>

Feb 23, 2013 9:57 AM in response to twtwtw

Hi twtwtw. I was a bit hasty with my appreciation. Good thing is launchd does not start the script at every start of the Mac, but it does not start the scripts at the given schedule either. When I start manually (terminal: launchctl start org.jenal.picture_backup) it works fine. But the schedule does not work. Do you think this is because of the AfterInitialDemand key?

Feb 23, 2013 11:17 AM in response to wondermac

a question: if you start the job manually (using the start command) does it run at the scheduled time after that? The reason I ask is that it makes a difference as to the next thing to try. If it does, then all you need is a kicker script to get the ball rolling; if it doesn't we'll have to shift to a different approach.


Sorry: the risk of using undocumented keys...

launchd ignores RunAtLoad=false

Welcome to Apple Support Community
A forum where Apple customers help each other with their products. Get started with your Apple ID.