¿Solución genérica para evitar que un trabajo cron largo se ejecute en paralelo?

27

Estoy buscando una solución simple y genérica que le permita ejecutar cualquier script o aplicación en crontab y evitar que se ejecute dos veces.

La solución debe ser independiente del comando ejecutado.

Supongo que debería verse como lock && (command ; unlock)donde el bloqueo devolverá falso si hubiera otro bloqueo.

La segunda parte sería como si adquiriera el bloqueo, ejecute el comando y desbloquee después de ejecutar el comando, incluso si devuelve un error.

Sorin
fuente

Respuestas:

33

Echa un vistazo al paquete run-oneInstalar run-one . Desde la página de manual para el run-onecomandoIcono de página de manual :

run-one es un script de contenedor que no ejecuta más de una instancia única de algún comando con un conjunto único de argumentos.

Esto suele ser útil con cronjobs, cuando no desea que se ejecute más de una copia a la vez.

Me gusta timeo sudo, simplemente lo antepone al comando. Entonces un cronjob podría verse así:

  */60 * * * *   run-one rsync -azP $HOME example.com:/srv/backup

Para obtener más información y antecedentes, consulte la publicación de blog que lo presenta Dustin Kirkland.

andrewsomething
fuente
5

Una forma muy sencilla de configurar un bloqueo:

if mkdir /var/lock/mylock; then
  echo "Locking succeeded" >&2
else
  echo "Lock failed - exit" >&2
  exit 1
fi

Las secuencias de comandos que desean ejecutarse deben crear el bloqueo. Si el bloqueo existe, otro script está ocupado, por lo que el primer script no puede ejecutarse. Si el archivo no existe, ningún script ha adquirido el bloqueo. Entonces el script actual adquiere el bloqueo. Cuando la secuencia de comandos ha finalizado, el bloqueo debe liberarse quitando el bloqueo.

Para obtener más información sobre las cerraduras bash, consulte esta página

OrangeTux
fuente
1
También querrás una trampa EXIT que elimine el bloqueo al salir. echo "Locking succeeded" >&2; trap 'rm -rf /var/lock/mylock' EXIT
geirha
1
Lo ideal sería utilizar una bandada de asesoramiento de un proceso que ejecuta el comando que desea como una subtarea. De esa manera, si todos mueren, el lote se libera automáticamente, lo que no funciona con la presencia de un archivo de bloqueo. El uso de un puerto de red funcionaría de manera similar, aunque ese es un espacio de nombres mucho más pequeño, lo cual es un problema.
Alex North-Keys
3

No es necesario instalar un paquete elegante:

#!/bin/bash
pgrep -xf "$*" > /dev/null || "$@"

Es más rápido escribir ese script usted mismo que ejecutar "apt-get install", ¿no es así? Es posible que desee agregar "-u $ (id -u)" al pgrep para verificar las instancias ejecutadas solo por el usuario actual.

Michael Kowhan
fuente
2
Esto no garantiza una sola instancia. dos scripts pueden pasar al otro lado del ||operador al mismo tiempo, antes de que cualquiera tenga la oportunidad de iniciar el script todavía.
Sedat Kapanoglu
@SedatKapanoglu De acuerdo, ese script no es a prueba de condiciones de carrera, pero la pregunta original era sobre los trabajos cron de larga ejecución (que se ejecutan como máximo una vez por minuto). Si su sistema necesita más de un minuto para la creación del proceso, tiene otros problemas. Sin embargo, si es necesario por alguna otra razón, puede usar flock (1) para proteger el script anterior contra las condiciones de carrera.
Michael Kowhan
Usé esto pero para un script bash que debería verificarse a sí mismo. El código es este: v = $ (pgrep -xf "/ bin / bash $ 0 $ @") ["$ {v / $ BASHPID /}"! = ""] && salir 2
ahofmann
3

Consulte también Tim Kay's solo, que realiza el bloqueo al vincular un puerto en una dirección de bucle de retorno único para el usuario:

http://timkay.com/solo/

En caso de que su sitio se caiga:

Uso:

solo -port=PORT COMMAND

where
    PORT        some arbitrary port number to be used for locking
    COMMAND     shell command to run

options
    -verbose    be verbose
    -silent     be silent

Úselo así:

* * * * * solo -port=3801 ./job.pl blah blah

Guión:

#!/usr/bin/perl -s
#
# solo v1.7
# Prevents multiple cron instances from running simultaneously.
#
# Copyright 2007-2016 Timothy Kay
# http://timkay.com/solo/
#
# It is free software; you can redistribute it and/or modify it under the terms of either:
#
# a) the GNU General Public License as published by the Free Software Foundation;
#    either version 1 (http://dev.perl.org/licenses/gpl1.html), or (at your option)
#    any later version (http://www.fsf.org/licenses/licenses.html#GNUGPL), or
#
# b) the "Artistic License" (http://dev.perl.org/licenses/artistic.html), or
#
# c) the MIT License (http://opensource.org/licenses/MIT)
#

use Socket;

alarm $timeout                              if $timeout;

$port =~ /^\d+$/ or $noport                     or die "Usage: $0 -port=PORT COMMAND\n";

if ($port)
{
    # To work with OpenBSD: change to
    # $addr = pack(CnC, 127, 0, 1);
    # but make sure to use different ports across different users.
    # (Thanks to  www.gotati.com .)
    $addr = pack(CnC, 127, $<, 1);
    print "solo: bind ", join(".", unpack(C4, $addr)), ":$port\n"   if $verbose;

    $^F = 10;           # unset close-on-exec

    socket(SOLO, PF_INET, SOCK_STREAM, getprotobyname('tcp'))       or die "socket: $!";
    bind(SOLO, sockaddr_in($port, $addr))               or $silent? exit: die "solo($port): $!\n";
}

sleep $sleep if $sleep;

exec @ARGV;
ADN
fuente
Para Mac OSX, esto fallará con Error a solo(3801): Can't assign requested addressmenos que fuerce un 0para el 3er parámetro del método pack. Lo que es bueno para el BSD también es bueno para la Mac.
Eric Leschinski
1

Necesitas un candado. run-onehace el trabajo, pero también puede que desee ver en flockde util-linuxpaquete.

Es un paquete estándar proporcionado por los desarrolladores del kernel, permite una mayor personalización run-oney aún es muy simple.

mosca de espuma de poliestireno
fuente
0

Una solución simple de bash-hackers.org que funcionó para mí fue usar mkdir . Esta es una manera fácil de asegurarse de que solo se esté ejecutando una instancia de su programa. Cree un directorio con mkdir .lock que devuelva

  • cierto si la creación fue exitosa y
  • falso si el archivo de bloqueo existe, lo que indica que actualmente hay una instancia ejecutándose.

Entonces, esta función simple hizo toda la lógica de bloqueo de archivos:

if mkdir .lock; then
    echo "Locking succeeded"
    eval startYourProgram.sh ;
else
    echo "Lock file exists. Program already running? Exit. "
    exit 1
fi


echo "Program finished, Removing lock."
rm -r .lock
domih
fuente
0

Esta solución es para un script bash que necesita verificarse

v=$(pgrep -xf "/bin/bash $0 $@")
[ "${v/$BASHPID/}" != "" ] && exit 0
ahofmann
fuente