¿Launchd puede ejecutar programas con más frecuencia que cada 10 segundos?

Tengo algunos servicios como este que me gustaría ejecutar casi inmediatamente después de modificar los archivos.

<?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>

Incluso si ThrottleInterval se estableció en 1 o 0, solo se ejecutan como máximo cada 10 segundos.

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.plistsolo dice que los programas no se ejecutan más de cada 10 segundos de forma predeterminada, pero no menciona que ThrottleInterval no se puede configurar por debajo de eso.

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.

Puede mantener el programa o script ejecutándose durante 10 segundos y observar los cambios cada segundo:

#!/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

O lo mismo en 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

Pero, ¿hay alguna forma de eludir o disminuir el límite de tiempo? También se aplica a las acciones de carpeta.

Respuestas (3)

No hay forma de eludir o disminuir el límite de tiempo.

La documentación de Apple con respecto a la creación de trabajos lanzados establece lo siguiente:

Importante Si su daemon se apaga demasiado rápido después de iniciarse, launchd puede pensar que se ha bloqueado. Los demonios que continúan con este comportamiento pueden suspenderse y no volver a iniciarse cuando lleguen solicitudes futuras. Para evitar este comportamiento, no lo apague durante al menos 10 segundos después del lanzamiento.

Su programa o secuencia de comandos debe seguir ejecutándose durante al menos 10 segundos. Considere implementar un ciclo para verificar las fechas de modificación de archivos en los últimos diez segundos, dormir durante diez segundos y repetir.

Alternativamente, puede ver archivos específicos utilizando las API de kqueue o FSEvents. Esta pregunta de StackOverflow puede ser útil, Notificación de cambio del sistema de archivos a nivel de archivo en Mac OS X.

Puede mantener su secuencia de comandos ejecutándose en un bucle buscando archivos modificados en lugar de salir cuando haya terminado. Haga que duerma durante unos segundos después de comprobar los archivos modificados. Si encuentra archivos modificados, continúe con el script. Si no, vuelve a dormir.

Luego, inicie su secuencia de comandos cada x minutos en caso de que la ejecución anterior falle. Codifique el comienzo de su secuencia de comandos para verificar si ya se está ejecutando otra instancia y, de ser así, ciérrela.

launchd no parece iniciar otra instancia si la anterior aún se está ejecutando.
launchd no lanzará varias instancias del mismo ticket de trabajo.

Si necesita iniciar un script con más frecuencia que cada 10 segundos, puede ser costoso en términos de "bifurcación" (léase: asignación de memoria, inicio de nuevos procesos, etc.).

Por lo tanto, en este caso es mejor escribir su propio " daemon " (programa, lo que se ejecuta en segundo plano)

Le recomiendo que use un lenguaje "más capaz" como BASH (mi favorito es "perl", pero Ruby también está bien) porque un buen demonio maneja tiempos de espera, alarmas, etc., cosas que son demasiado difíciles de implementar en bash puro. (Por supuesto, el demonio también puede ejecutar sus scripts bash, si es necesario). Los básicos son:

  • escribe lo que se está ejecutando sin fin y esperando algún evento. El evento puede ser una entrada de red, un temporizador simple o algo parecido. Cuando llega el evento (por ejemplo, el estado de espera terminó), el script hará lo que desee y el ciclo se repetirá.

En el mundo perl ya existen módulos que configuran su script como un proceso "daemon", por ejemplo Proc::Daemon . No tengo experiencia con Ruby, pero este artículo puede ayudarte.

Puede iniciar su proceso de daemon a través de Launchd al inicio del sistema, o desde la aplicación de automatización cuando inicia sesión, o desde la Terminal manualmente.