Take the 2-minute tour ×
Ask Different is a question and answer site for power users of Apple hardware and software. It's 100% free, no registration required.

I have some services like this that I'd like to run almost immediately after files are modified.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>test</string>
    <key>ProgramArguments</key>
    <array>     
        <string>say</string>
        <string>a</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>/Users/username/Desktop/</string>
    </array>
</dict>
</plist>

Even if ThrottleInterval was set to 1 or 0, they are only run at most every 10 seconds.

9/9/12 4:57:05.457 PM com.apple.launchd.peruser.501[138]: (test) Throttling respawn: Will start in 7 seconds
9/9/12 4:57:09.541 PM com.apple.launchd.peruser.501[138]: (test) Throttling respawn: Will start in 3 seconds

man launchd.plist only says that programs aren't run more than every 10 seconds by default, but doesn't mention that ThrottleInterval couldn't be set below that.

ThrottleInterval <integer>
This key lets one override the default throttling policy imposed on jobs by launchd.
The value is in seconds, and by default, jobs will not be spawned more than once
every 10 seconds.  The principle behind this is that jobs should linger around just
in case they are needed again in the near future. This not only reduces the latency
of responses, but it encourages developers to amortize the cost of program invoca-
tion.

You could keep the program or script running for 10 seconds and watch for changes every second:

#!/bin/bash

start=$(date +%s)
prev=

until (( $(date +%s) >= $start + 10 )); do
    new=$(stat -f %m ~/Desktop/)
    [[ $prev != $new ]] && say a
    prev=$new
    sleep 1
done

Or the same in Ruby:

#!/usr/bin/env ruby

start = Time.now
prev = nil

until Time.now >= start + 10
  current = File.mtime("#{ENV['HOME']}/Desktop/")
  `say a` if current != prev
  prev = current
  sleep 1
end

But is there some way to bypass or decrease the time limit? It also applies to folder actions.

share|improve this question

3 Answers 3

up vote 4 down vote accepted

There is no way to bypass or decrease the time limit.

Apple's documentation regarding Creating Launchd Jobs states the following:

Important If your daemon shuts down too quickly after being launched, launchd may think it has crashed. Daemons that continue this behavior may be suspended and not launched again when future requests arrive. To avoid this behavior, do not shut down for at least 10 seconds after launch.

Your program or script needs to keep running for at least 10 seconds. Consider implementing a loop to check for file modification dates in the last ten seconds, sleeping for ten seconds, and repeating.

Alternatively, you can watch specific files using the kqueue or FSEvents APIs. This StackOverflow question may be helpful, File-level filesystem change notification in Mac OS X.

share|improve this answer

You could keep your script running in a loop checking for modified files instead of quitting when it is done. Have it sleep for a few seconds after checking for the modified files. If it finds modified files, continue with the script. If not, sleep again.

Then have launchd start your script every x minutes just in case the previous run dies. Code the beginning of your script to check if another instance is already running and if so, quit itself.

share|improve this answer
    
launchd doesn't seem to start another instance if the previous one is still running. –  ؘؘؘ Sep 11 '12 at 15:26
    
launchd will not launch multiple instances of the same job ticket. –  Graham Miln Sep 11 '12 at 17:13

If you need start a script more often than every 10 seconds, it can be expensive in term of "forking" (read: allocating memory, starting new processes etc).

Therefore, in this case is the best to write your own "daemon" (program, what runs in the background)

I recommend you use an "more capable" language as BASH (my favorite is "perl", but ruby is OK too) because a good daemon handles timeouts, alarms and so on - things what is too hard implement in pure bash. (Of course, the daemon can run your bash scripts too - if needed). The basics are:

  • script what is running endless and waiting for some event. The event can be some network input, or simple timer or anything like. When the event arrives (e.g. the wait state ended) the script will do what you want and the cycle repeats.

In the perl world already exists modules what tuns your script as "daemon" process, for example Proc::Daemon. I'm not experienced with ruby, but this article can help you.

You can start your daemon process via Launchd at the system startup, or from automator app when you logging in, or from the Terminal manually.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.