Solução genérica para impedir que um trabalho cron longo seja executado em paralelo?

27

Estou procurando uma solução simples e genérica que permita executar qualquer script ou aplicativo no crontab e impedir que ele seja executado duas vezes.

A solução deve ser independente do comando executado.

Suponho que deve parecer lock && (command ; unlock)onde o bloqueio retornará falso se houver outro bloqueio.

A segunda parte seria como se ele adquirisse o bloqueio, execute o comando e desbloqueie após a execução do comando, mesmo que retorne um erro.

sorin
fonte

Respostas:

33

Dê uma olhada no pacote executar umInstale o run-one . Na página de manual do run-onecomandoÍcone da página de manual :

run-one é um script de wrapper que executa não mais que uma instância exclusiva de algum comando com um conjunto exclusivo de argumentos.

Isso geralmente é útil com cronjobs, quando você não deseja mais de uma cópia em execução por vez.

Como timeou sudo, você apenas o anexa ao comando. Portanto, um cronjob pode se parecer com:

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

Para mais informações e informações, confira a postagem de blog de Dustin Kirkland.

andrewsomething
fonte
5

Uma maneira muito simples de configurar um bloqueio:

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

Um script que deseja executar precisa criar o bloqueio. Se o bloqueio existir, outro script estará ocupado, portanto, o primeiro script não poderá ser executado. Se o arquivo não existir, nenhum script adquiriu o bloqueio. Portanto, o script atual adquire o bloqueio. Quando o script terminar, o bloqueio precisará ser liberado removendo-o.

Para obter mais informações sobre bloqueios do bash, consulte esta página.

OrangeTux
fonte
1
Você também vai querer uma armadilha EXIT que remove a trava na saída. echo "Locking succeeded" >&2; trap 'rm -rf /var/lock/mylock' EXIT
Geirha 25/05
1
Idealmente, você deseja usar o rebanho consultivo de um processo que executa o comando que você deseja como subtarefa. Dessa forma, se todos eles morrem, o rebanho é liberado automaticamente, o que usando a presença de um arquivo de bloqueio não funciona. Usando uma porta de rede funcionaria de forma semelhante - embora isso seja uma maneira namespace menor, o que é um problema.
Alex-North-Keys
3

Não é necessário instalar um pacote sofisticado:

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

É mais rápido escrever esse script do que executar "apt-get install", não é? Você pode adicionar "-u $ (id -u)" ao pgrep para verificar instâncias executadas apenas pelo usuário atual.

Michael Kowhan
fonte
2
isso não garante uma única instância. dois scripts podem passar para o outro lado do ||operador ao mesmo tempo, antes que um deles tenha a chance de iniciar o script.
Sedat Kapanoglu
@SedatKapanoglu Concedido, esse script não é à prova de condições de corrida, mas a pergunta original era sobre cron-jobs de execução longa (que são executados no máximo uma vez por minuto). Se o seu sistema precisar de mais de um minuto para a criação do processo, você terá outros problemas. No entanto, se necessário por outras razões, você pode usar o flock (1) para proteger o script acima contra as condições da corrida.
Michael Kowhan
Eu usei isso, mas para um script bash que deve verificar-se. O código é este: v = $ (pgrep -xf "/ bin / bash $ 0 $ @") ["$ {v / $ BASHPID /}"! = ""] && exit 2
ahofmann
3

Consulte também Tim Kay's solo, que executa o bloqueio vinculando uma porta em um endereço de loopback exclusivo para o usuário:

http://timkay.com/solo/

Caso seu site seja desativado:

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

Use-o assim:

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

Roteiro:

#!/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;
DNA
fonte
No Mac OSX, isso falhará com Erro, a solo(3801): Can't assign requested addressmenos que você force a 0para o terceiro parâmetro do método pack. O que é bom para o BSD também é bom para o Mac.
Eric Leschinski 5/03
1

Você precisa de uma fechadura. run-onefaz o trabalho, mas você também pode querer olhar para flocka partir util-linuxdo pacote.

É um pacote padrão fornecido pelos desenvolvedores do kernel, permite mais personalização do que run-onee ainda é muito simples.

mosca de isopor
fonte
0

Uma solução simples do bash-hackers.org que funcionou para mim estava usando o mkdir . Essa é uma maneira fácil de garantir que apenas uma instância do seu programa esteja em execução. Crie um diretório com mkdir .lock que retorne

  • verdade se a criação foi bem sucedida e
  • false se o arquivo de bloqueio existir, indicando que existe atualmente uma instância em execução.

Portanto, essa função simples fez toda a lógica de bloqueio de arquivos:

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
fonte
0

Esta solução é para um script bash que precisa se verificar

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