En ocasiones, debe asegurarse de que solo se ejecute una instancia de un script de shell al mismo tiempo.
Por ejemplo, un trabajo cron que se ejecuta a través de crond que no proporciona bloqueo por sí solo (por ejemplo, el crond de Solaris predeterminado).
Un patrón común para implementar el bloqueo es un código como este:
#!/bin/sh
LOCK=/var/tmp/mylock
if [ -f $LOCK ]; then # 'test' -> race begin
echo Job is already running\!
exit 6
fi
touch $LOCK # 'set' -> race end
# do some work
rm $LOCK
Por supuesto, dicho código tiene una condición de carrera. Hay una ventana de tiempo donde la ejecución de dos instancias puede avanzar después de la línea 3 antes de que uno pueda tocar el $LOCK
archivo.
Para un trabajo cron, esto generalmente no es un problema porque tiene un intervalo de minutos entre dos invocaciones.
Pero las cosas pueden salir mal, por ejemplo, cuando el archivo de bloqueo está en un servidor NFS, que se cuelga. En ese caso, varios trabajos cron pueden bloquearse en la línea 3 y hacer cola. Si el servidor NFS está activo de nuevo, entonces usted tiene rebaño atronadores de puestos de trabajo funcionando en paralelo.
Al buscar en la web encontré la herramienta lockrun que parece una buena solución para ese problema. Con él ejecutas un script que necesita bloquearse así:
$ lockrun --lockfile=/var/tmp/mylock myscript.sh
Puede poner esto en un contenedor o usarlo desde su crontab.
Utiliza lockf()
(POSIX) si está disponible y recurre a flock()
(BSD). Y el lockf()
soporte sobre NFS debería estar relativamente extendido.
¿Hay alternativas a lockrun
?
¿Qué pasa con otros demonios cron? ¿Hay crond comunes que admiten el bloqueo de una manera sensata? Un vistazo rápido a la página de manual de Vixie Crond (predeterminado en los sistemas Debian / Ubuntu) no muestra nada sobre el bloqueo.
¿Sería una buena idea incluir una herramienta como lockrun
en coreutils ?
En mi opinión, implementa un tema muy similar a timeout
, nice
y amigos.
kill
; y parece ser una buena práctica almacenar el propio pid en el archivo de bloqueo, en lugar de solo tocarlo.Respuestas:
Aquí hay otra forma de bloquear el script de shell que puede evitar la condición de carrera que describió anteriormente, donde dos trabajos pueden pasar la línea 3. La
noclobber
opción funcionará en ksh y bash. No lo useset noclobber
porque no debería estar creando scripts en csh / tcsh. ;)YMMV con bloqueo en NFS (ya sabes, cuando los servidores NFS no son accesibles), pero en general es mucho más robusto de lo que solía ser. (Hace 10 años)
Si tiene trabajos cron que hacen lo mismo al mismo tiempo, desde varios servidores, pero solo necesita 1 instancia para ejecutarse, algo como esto podría funcionar para usted.
No tengo experiencia con lockrun, pero tener un entorno de bloqueo preestablecido antes de que el script realmente se ejecute podría ayudar. O tal vez no. Solo está configurando la prueba para el archivo de bloqueo fuera de su script en un contenedor, y en teoría, ¿no podría simplemente alcanzar la misma condición de carrera si lockrun llamara a dos trabajos exactamente al mismo tiempo, al igual que con el 'inside- ¿La solución del script?
De todos modos, el bloqueo de archivos es más bien un comportamiento del sistema de honor, y cualquier script que no verifique la existencia del archivo de bloqueo antes de ejecutarse hará lo que sea que vayan a hacer. Simplemente al poner a prueba el archivo de bloqueo y el comportamiento adecuado, resolverá el 99% de los problemas potenciales, si no el 100%.
Si se encuentra mucho en condiciones de carrera de archivo de bloqueo, puede ser un indicador de un problema mayor, como no tener sus trabajos cronometrados correctamente, o tal vez si el intervalo no es tan importante como completar el trabajo, tal vez su trabajo sea más adecuado para ser demonizado .
EDITAR ABAJO - 2016-05-06 (si está utilizando KSH88)
Base en el comentario de @Clint Pachl a continuación, si usa ksh88, use en
mkdir
lugar denoclobber
. Esto mitiga principalmente una posible condición de carrera, pero no la limita por completo (aunque el riesgo es minúsculo). Para obtener más información, lea el enlace que Clint publicó a continuación .Y, como ventaja adicional, si necesita crear archivos tmp en su secuencia de comandos, puede usar el
lockdir
directorio para ellos, sabiendo que se limpiarán cuando salga la secuencia de comandos.Para una fiesta más moderna, el método noclobber en la parte superior debería ser adecuado.
fuente
lockf()
llamada del sistema; cuando se realiza una copia de seguridad, todos los procesos se reanudan, pero solo un proceso ganará el bloqueo. Sin condición de carrera. No tengo muchos problemas con los cronjobs, lo contrario es el caso, pero esto es un problema cuando te golpea, tiene el potencial de crear mucho dolor.set -o noclobber && echo "$$" > "$lockfile"
para obtener una recuperación segura cuando el shell no admite la opción noclobber.noclobber
opción puede ser propensa a las condiciones de carrera. Vea mywiki.wooledge.org/BashFAQ/045 para obtener algo de reflexión.noclobber
(o-C
) en ksh88 no funciona porque ksh88 no se usaO_EXCL
paranoclobber
. Si está ejecutando con un shell más nuevo, puede estar bien ...Prefiero usar enlaces duros.
Los enlaces duros son atómicos sobre NFS y, en su mayor parte, mkdir también lo es . Usando
mkdir(2)
olink(2)
son casi lo mismo, en un nivel práctico; Simplemente prefiero usar enlaces duros porque más implementaciones de NFS permitieron enlaces duros atómicos que atómicosmkdir
. Con los lanzamientos modernos de NFS, no debería tener que preocuparse por usar ninguno.fuente
Entiendo que
mkdir
es atómico, así que quizás:fuente
mkdir()
sobre si NFS (> = 3) está estandarizado para ser atómico.mkdir
que sea atómico (sí lo hacerename
). En la práctica, se sabe que algunas implementaciones no lo son. Relacionado: un hilo interesante, que incluye una contribución del autor de GNU arch .Una manera fácil es usar
lockfile
elprocmail
paquete que viene generalmente .fuente
sem
que viene como parte de lasparallel
herramientas de GNU puede ser lo que estás buscando:Como en:
salida:
Tenga en cuenta que el orden no está garantizado. Además, la salida no se muestra hasta que finaliza (¡irritante!). Pero aun así, es la forma más concisa que conozco para evitar la ejecución simultánea, sin preocuparme por los archivos de bloqueo, los reintentos y la limpieza.
fuente
sem
mango se derriba a mitad de la ejecución?Yo uso
dtach
.fuente
Utilizo la herramienta de línea de comandos "flock" para administrar bloqueos en mis scripts de bash, como se describe aquí y aquí . He usado este método simple desde la página de manual de flock, para ejecutar algunos comandos en una subshell ...
En ese ejemplo, falla con el código de salida de 1 si no puede adquirir el archivo de bloqueo. Pero el lote también se puede usar de manera que no requiera que los comandos se ejecuten en un sub-shell :-)
fuente
flock()
llamada al sistema no funciona a través de NFS .flock()
y, por lo tanto, es problemático en NFS. Por cierto, mientras tanto, flock () en Linux ahora recurre afcntl()
cuando el archivo se encuentra en un montaje NFS, por lo tanto, en un entorno NFS solo para Linuxflock()
ahora funciona sobre NFS.No uses un archivo.
Si su script se ejecuta así, por ejemplo:
Puede detectar si se está ejecutando usando:
fuente
my_script
? En el caso de que se ejecute otra instancia, ¿norunning_proc
contiene dos líneas coincidentes? Me gusta la idea, pero, por supuesto, obtendrás resultados falsos cuando otro usuario ejecute un script con el mismo nombre ...$!
lugar de$$
en tu ejemplo.grep -v $$
). Básicamente estaba intentando proporcionar un enfoque diferente al problema.Para uso real, debe usar la respuesta más votada .
Sin embargo, quiero hablar sobre varios enfoques rotos y semi-viables que usan
ps
y las muchas advertencias que tienen, ya que sigo viendo que la gente los usa.Esta respuesta es realmente la respuesta a "¿Por qué no usar
ps
ygrep
manejar el bloqueo en el shell?"Enfoque roto # 1
Primero, un enfoque dado en otra respuesta que tiene algunos votos positivos a pesar de que no funciona (y nunca podría funcionar) y claramente nunca se probó:
Arreglemos los errores de sintaxis y los
ps
argumentos rotos y obtengamos:Este script siempre saldrá de 6, siempre, sin importar cómo lo ejecute.
Si lo ejecuta con
./myscript
, entonces laps
salida será12345 -bash
, que no coincide con la cadena requerida12345 bash ./myscript
, por lo que fallará.Si lo ejecutas
bash myscript
, las cosas se ponen más interesantes. El proceso bash se bifurca para ejecutar la canalización, y el shell secundario ejecuta elps
ygrep
. Tanto el shell original como el shell hijo aparecerán en laps
salida, algo así:Esa no es la salida esperada
$$ bash $0
, por lo que su script se cerrará.Enfoque roto # 2
Ahora, para ser justos con el usuario que escribió el enfoque roto # 1, hice algo similar cuando probé esto por primera vez:
Esto casi funciona. Pero el hecho de bifurcarse para hacer correr la tubería arroja esto. Así que este siempre saldrá, también.
Enfoque poco confiable # 3
Esta versión evita el problema de la bifurcación de la tubería en el enfoque n. ° 2 al obtener primero todos los PID que tienen el script actual en sus argumentos de línea de comando y luego filtrar esa lista pid, por separado, para omitir el PID del script actual.
Esto podría funcionar ... siempre que ningún otro proceso tenga una línea de comando que coincida con
$0
, y siempre que el script se llame de la misma manera (por ejemplo, si se llama con una ruta relativa y luego una ruta absoluta, la última instancia no notará la primera )Enfoque poco confiable # 4
Entonces, ¿qué pasa si omitimos la verificación de la línea de comando completa, ya que eso podría no indicar que un script se está ejecutando realmente y, en su
lsof
lugar , verificamos para encontrar todos los procesos que tienen abierto este script?Bueno, sí, este enfoque en realidad no es tan malo:
Por supuesto, si se está ejecutando una copia del script, la nueva instancia se iniciará perfectamente y tendrá dos copias ejecutándose.
O si se modifica la secuencia de comandos en ejecución (por ejemplo, con Vim o con a
git checkout
), entonces la "nueva" versión de la secuencia de comandos se iniciará sin problemas, ya que tanto Vim comogit checkout
un nuevo archivo (un nuevo inodo) en lugar del el viejo.Sin embargo, si el script nunca se modifica y nunca se copia, entonces esta versión es bastante buena. No hay condición de carrera porque el archivo de secuencia de comandos ya debe estar abierto antes de que se pueda alcanzar la verificación.
Todavía puede haber falsos positivos si otro proceso tiene el archivo de script abierto, pero tenga en cuenta que incluso si está abierto para editar en Vim, vim no mantiene abierto el archivo de script, por lo que no dará lugar a falsos positivos.
Pero recuerde, no use este enfoque si el script se puede editar o copiar, ya que obtendrá falsos negativos, es decir, se ejecutarán varias instancias a la vez, por lo que el hecho de editar con Vim no da falsos positivos. para ti. Lo menciono, sin embargo, porque el enfoque # 3 no dar falsos positivos (es decir, se niega a iniciar) si tiene el guión abierto con Vim.
Entonces, ¿qué hacer, entonces?
La respuesta más votada a esta pregunta ofrece un buen enfoque sólido.
Quizás pueda escribir uno mejor ... pero si no comprende todos los problemas y advertencias con todos los enfoques anteriores, es probable que no escriba un método de bloqueo que los evite a todos.
fuente
Usando la herramienta FLOM (Free LOck Manager) , serializar comandos se vuelve tan fácil como ejecutar
FLOM le permite implementar casos de uso más sofisticados (bloqueo distribuido, lectores / escritores, recursos numéricos, etc.) como se explica aquí: http://sourceforge.net/p/flom/wiki/FLOM%20by%20examples/
fuente
Aquí hay algo que a veces agrego en un servidor para manejar fácilmente las condiciones de carrera para cualquier trabajo en la máquina. Es similar a la publicación de Tim Kennedy, pero de esta manera obtienes el manejo de la carrera al agregar solo una fila a cada script de bash que lo necesita.
Ponga el contenido a continuación en, por ejemplo, / opt / racechecker / racechecker:
Aquí está cómo usarlo. Tenga en cuenta la fila después del shebang:
La forma en que funciona es que determina el nombre del archivo bashscript principal y crea un archivo pid en "/ tmp". También agrega un oyente a la señal de finalización. El oyente eliminará el archivo pid cuando el script principal esté terminando correctamente.
En cambio, si existe un archivo pid cuando se inicia una instancia, se ejecutará la instrucción if que contiene el código dentro de la segunda instrucción if. En este caso, he decidido lanzar un correo de alarma cuando esto sucede.
¿Qué pasa si el script falla
Otro ejercicio sería manejar los accidentes. Idealmente, el archivo pid debería eliminarse incluso si el script principal falla por algún motivo, esto no se hace en mi versión anterior. Eso significa que si el script falla, el archivo pid debería eliminarse manualmente para restaurar la funcionalidad.
En caso de bloqueo del sistema
Es una buena idea almacenar el archivo pidfile / lockfile en, por ejemplo, / tmp. De esta forma, sus secuencias de comandos continuarán ejecutándose definitivamente después de un bloqueo del sistema, ya que los archivos pid siempre se eliminarán en el arranque.
fuente
Comprueba mi guión ...
Te puede ENCANTAR ...
fuente
Ofrezco la siguiente solución, en un script llamado 'flocktest'
fuente