Pergunta

Eu estou apostando que alguém já tenha resolvido isso e talvez eu estou usando os termos de pesquisa erradas para o Google para me dizer a resposta, mas aqui está a minha situação.

Eu tenho um script que eu quero correr, mas eu quero que ele seja executado somente quando programado e só um de cada vez. (Não é possível executar o script em simultâneo)

Agora, a parte mais complicada é que dizem que eu tenho uma tabela chamada "myhappyschedule", que tem os dados que eu preciso e da hora programada. Esta tabela pode ter vários agendada vezes, até mesmo, ao mesmo tempo, cada um iria executar este script. Então, basicamente eu preciso de uma fila de cada vez que os fogos de script e todos eles necessidade de esperar por cada um deles antes que ela termine. (Às vezes isso pode levar apenas um minuto para o script para executar, por vezes, os seus muitos minutos)

O que eu estou pensando em fazer é fazer um script que verifica myhappyschedule a cada 5 minutos e reúne-se aqueles que estão programados, coloca-los em uma fila onde um outro script pode executar cada 'trabalho' ou ocorrência na fila em ordem. Que tudo isso soa confuso.

Para fazer este mais - Devo dizer que estou permitindo que os usuários para as coisas de programação em myhappyschedule e não editar crontab.

O que pode ser feito sobre isso? bloqueios de arquivos e scripts que chamam de scripts?

Foi útil?

Solução

adicionar um exec_status coluna para myhappytable (talvez também time_started e time_finished, ver pseudocódigo)

execute o seguinte script cron cada x minutos

pseudocódigo de script cron:

[create/check pid lock (optional, but see "A potential pitfall" below)]
get number of rows from myhappytable where (exec_status == executing_now)
if it is > 0, exit
begin loop
  get one row from myhappytable
    where (exec_status == not_yet_run) and (scheduled_time <= now)
    order by scheduled_time asc
  if no such row, exit
  set row exec_status to executing_now (maybe set time_started to now)
  execute whatever command the row contains
  set row exec_status to completed
  (maybe also store the command output/return as well, set time_finished to now)
end loop
[delete pid lock file (complementary to the starting pid lock check)]

Desta forma, o script primeiro verifica se nenhum dos comandos está em execução, em seguida, executa primeiro não-ainda comando executar, até que não haja mais comandos a serem executados no momento dado. Além disso, você pode ver o que o comando está sendo executado por meio de consulta ao banco de dados.

Uma armadilha potencial: se o script cron é morto, uma tarefa agendada permanecerá no estado "executing_now". Isso é o que o bloqueio pid no início e final é a seguinte: para ver se o script cron terminado corretamente. pseudocode de criar / verificação pidlock:

if exists pidlockfile then
  check if process id given in file exists
  if not exists then
    update myhappytable set exec_status = error_cronscript_died_while_executing_this   
      where exec_status == executing_now
    delete pidlockfile
  else (previous instance still running)
    exit
  endif
endif
create pidlockfile containing cron script process id

Outras dicas

Você pode usar o comando at (1) dentro do seu script para agendar a sua próxima corrida. Antes de sair, ele pode verificar myhappyschedule para a próxima tempo de execução. Você não precisa de cron em tudo, na verdade.

me deparei com esta questão enquanto pesquisava para uma solução para o problema de filas. Para o benefício de qualquer outra pessoa à procura aqui é a minha solução.

Combine isso com um cron que os trabalhos começa como eles estão programadas (mesmo se eles são programados para executar ao mesmo tempo) e que resolve o problema que você descreveu tão bem.

Problema


  • No máximo uma instância do script deve estar em execução.
  • Queremos taco até pedidos para processá-los o mais rápido possível.

ie. Precisamos de um gasoduto para o script.

Solução:


Criar um gasoduto para qualquer script. Feito usando um script pequeno (mais abaixo).

O script pode ser chamado como
./pipeline "<any command and arguments go here>"

Exemplo:

./pipeline sleep 10 &
./pipeline shabugabu &
./pipeline single_instance_script some arguments &
./pipeline single_instance_script some other_argumnts &
./pipeline "single_instance_script some yet_other_arguments > output.txt" &
..etc

O script cria um novo pipe nomeado para cada comando. Assim, os tubos acima criará nomeados: sleep, shabugabu e single_instance_script

Neste caso, a chamada inicial será iniciado um single_instance_script leitor e correr com some arguments como argumentos. Depois da conclusão do atendimento, o leitor vai agarrar a próxima solicitação fora do tubo e executar com some other_arguments, completo, pegue a próxima etc ...

Este script irá bloquear solicitando processos para chamá-lo como um trabalho de fundo (e no final) ou como um processo independente com at (at now <<< "./pipeline some_script")

#!/bin/bash -Eue

# Using command name as the pipeline name
pipeline=$(basename $(expr "$1" : '\(^[^[:space:]]*\)')).pipe
is_reader=false

function _pipeline_cleanup {
        if $is_reader; then
                rm -f $pipeline
        fi
        rm -f $pipeline.lock

        exit
}
trap _pipeline_cleanup INT TERM EXIT

# Dispatch/initialization section, critical
lockfile $pipeline.lock
        if [[ -p $pipeline ]]
        then
                echo "$*" > $pipeline
                exit
        fi

        is_reader=true
        mkfifo $pipeline
        echo "$*" > $pipeline &
rm -f $pipeline.lock

# Reader section
while read command < $pipeline
do
        echo "$(date) - Executing $command"
        ($command) &> /dev/null
done
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top