Script Bash para sinalizar uma instância em execução simultânea (mesmo script) sobre uma condição e um contador de incremento

2

Antecedentes

Aqui está o que eu quero alcançar. Eu tenho um processo (potencialmente) de longa duração. Agora tenho um bom encaixe e tudo está em ordem.

No entanto, como este é um trabalho agendado, o script será executado novamente e novamente. No script real que tenho, um arquivo registra essa contagem de falhas. Quando atinge um limite, um e-mail é enviado para o administrador, caso o script original se torne um zumbi ou esteja preso. Se o limite for atingido e o processo inicial que afirma que o bloqueio não está mais ativo, o bloqueio é interrompido e um texto alternativo é enviado ao administrador informando sobre a condição. Tudo isso provavelmente não é relevante para o problema, mas quero dar uma visão completa aqui.

Sempre que o limite é atingido, a contagem de falhas no arquivo é redefinida e inicia novamente a partir daí. A instância original pode ou não não conseguir ver o arquivo de contagem de falhas.

A instância de script original que segurou o bloqueio terminará (esperançosamente) seu trabalho e sairá.

Pergunta

Como posso implementar um esquema pelo qual as instâncias "com falha" que encontram o bloqueio enviam um sinal (tentei SIGUSR1 ) para a instância original e a instância original controla quantas vezes ele foi "pingado"?

Aqui está o script de teste que eu criei até agora:

#!/usr/bin/env bash
LOCKFILE=/tmp/$(basename $0).lock
let COUNTER=0

function concurrent_run
{
    let COUNTER=COUNTER+1
    echo -e "\ncaught SIGUSR1 (counter = $COUNTER)"
}

if ( set -o noclobber; echo "$$" > "$LOCKFILE" ) 2> /dev/null; then
    trap 'rm -f "$LOCKFILE"; exit $?' INT TERM EXIT
    trap concurrent_run USR1
    echo "I was first, going to sleep"
    sleep 100000
    trap - INT TERM EXIT USR1
else
    LOCKPID=$(cat "$LOCKFILE")
    echo "Lock already held by $LOCKPID"
    [[ -n "$LOCKPID" ]] && kill -0 $LOCKPID && { kill -USR1 $LOCKPID; echo "... knock knock? ($LOCKPID)"; }
    exit 1
fi
echo "End: $COUNTER"
exit 0

(o sleep está imitando o trabalho de longa duração executado pelo script real)

Estou testando no Linux e tentei o nome do sinal como USR1 e SIGUSR1 , ambos fornecendo o mesmo resultado (ou seja, nenhum).

builtin trap -l

me deu 10 como SIGUSR1 , então também tentei um manual kill -10 PID no PID da instância original. Infelizmente não consigo ver o sinal.

O que estou fazendo de errado?

Como testar

Inicie uma instância do script (eu a denominei sigtest ):

$ ./sigtest
I was first, going to sleep

Inicie uma segunda instância do script em outro terminal, janela, painel (mesmo usuário!):

$ ./sigtest
Lock already held by 15360
... knock knock? (15360)

Repita ...

Resultado esperado no primeiro terminal (ignorando linhas vazias):

caught SIGUSR1 (counter = 1)
caught SIGUSR1 (counter = 2)
caught SIGUSR1 (counter = 3)
caught SIGUSR1 (counter = 4)

Versão do Bash

$ echo $BASH_VERSION
4.1.5(1)-release
    
por 0xC0000022L 27.02.2014 / 20:10

2 respostas

3

man bash:

If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes. When bash is waiting for an asynchronous command via the wait builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return immediately with an exit status greater than 128, immediately after which the trap is executed.

Gere sleep 100 e aguarde que ele termine. Eu não sei se vários sinais são manipulados várias vezes, no entanto.

    
por 27.02.2014 / 20:21
0

Você pode comunicar a contagem para o trabalho de longa duração de duas formas gerais:

  1. Um contador compartilhado protegido por um segundo bloqueio; ou
  2. Uma coleção de contadores não compartilhados que são agregados pelo trabalho de longa duração.

O contador compartilhado é bastante simples:

if (set -o noclobber; echo $$ > /tmp/grumpy-waiters.lock); then
  echo >> /tmp/grumpy-waiters
  rm -f /tmp/grumpy-waiters.lock
fi

O trabalho de longa duração pode ler o contador da seguinte forma:

num_grumpy_waiters=0
if [ -s /tmp/grumpy-waiters ]; then
  num_grumpy_waiters=$(wc -c < /tmp/grumpy-waiters)
fi

A abordagem de coleção não compartilhada envolve a criação de vários arquivos sem necessidade de bloqueio. Isso é menos arriscado, mas mais elaborado. Cada instância com falha cria ou atualiza seu próprio contador:

grumpy_shard=1
if [ -s /tmp/grumpy.${LOCKPID}.$$ ]; then
  grumpy_shard=$(expr $(cat /tmp/grumpy.${LOCKPID}.$$) + 1)
fi
echo ${grumpy_shard} > /tmp/grumpy.${LOCKPID}.$$

A instância de longa execução pode pegar esses contadores e agregá-los logo após liberar o bloqueio mestre:

num_grumpy_waiters=$(cat /tmp/grumpy.$$.* 2>/dev/null | awk 'BEGIN{s=0}NF>0{s=s+int($1)}END{print s}')
rm -f /tmp/grumpy.$$.* 2>/dev/null

Em qualquer caso, o uso de sinais para interromper o script de shell não causará uma interrupção no tempo. Se você precisar disso, o script precisará bifurcar um ouvinte em seu próprio plano de fundo para que ele possa responder a um sinal imediatamente.

    
por 28.02.2014 / 10:35