In che modo questo script garantisce che sia in esecuzione solo un'istanza?


22

Il 19 agosto 2013, Randal L. Schwartz ha pubblicato questo script di shell, che aveva lo scopo di garantire, su Linux, "che sia in esecuzione solo un'istanza di [lo] script, senza condizioni di competizione o che debba ripulire i file di blocco":

#!/bin/sh
# randal_l_schwartz_001.sh
(
    if ! flock -n -x 0
    then
        echo "$$ cannot get flock"
        exit 0
    fi
    echo "$$ start"
    sleep 10 # for testing.  put the real task here
    echo "$$ end"
) < $0

Sembra funzionare come pubblicizzato:

$ ./randal_l_schwartz_001.sh & ./randal_l_schwartz_001.sh
[1] 11863
11863 start
11864 cannot get flock
$ 11863 end

[1]+  Done                    ./randal_l_schwartz_001.sh
$

Ecco cosa capisco:

  • Lo script reindirizza ( <) una copia del proprio contenuto (ovvero da $0) allo STDIN (ovvero descrittore di file 0) di una subshell.
  • All'interno della subshell, lo script tenta di ottenere un blocco esclusivo ( flock -n -x) non bloccante sul descrittore di file 0.
    • Se quel tentativo fallisce, la subshell esce (e così fa lo script principale, poiché non c'è nient'altro da fare).
    • Se invece il tentativo ha esito positivo, la subshell esegue l'attività desiderata.

Ecco le mie domande:

  • Perché lo script deve reindirizzare, a un descrittore di file ereditato dalla subshell, una copia del proprio contenuto anziché, diciamo, il contenuto di qualche altro file? (Ho provato a reindirizzare da un file diverso e rieseguire come sopra, e l'ordine di esecuzione è cambiato: l'attività non in background ha ottenuto il blocco prima di quello in background. Quindi, forse usando i contenuti del file si evitano le condizioni di gara; ma come?)
  • Perché lo script deve reindirizzare, a un descrittore di file ereditato dalla subshell, una copia del contenuto di un file, comunque?
  • Perché tenere un blocco esclusivo sul descrittore di file 0in una shell impedisce a una copia dello stesso script, eseguita in una shell diversa, di ottenere un blocco esclusivo sul descrittore di file 0? Non conchiglie hanno le proprie, copie separate dei descrittori di file standard ( 0, 1, e 2, cioè STDIN, stdout e stderr)?

Qual è stato il tuo esatto processo di test quando hai provato l'esperimento per reindirizzare da un file diverso?
Freiheit,

1
Penso che tu possa fare riferimento a questo link. stackoverflow.com/questions/185451/…
Deb Paikar

Risposte:


22

Perché lo script deve reindirizzare, a un descrittore di file ereditato dalla subshell, una copia del proprio contenuto anziché, diciamo, il contenuto di qualche altro file?

È possibile utilizzare qualsiasi file, purché tutte le copie dello script utilizzino lo stesso. Usare $0solo il blocco dello script stesso: se copi lo script e lo modifichi per qualche altro uso, non è necessario trovare un nuovo nome per il file di blocco. Questo è conveniente

Se lo script viene chiamato tramite un collegamento simbolico, il blocco si trova sul file effettivo e non sul collegamento.

(Naturalmente, se qualche processo esegue lo script e gli conferisce un valore inventato come argomento zeroth invece del percorso effettivo, allora questo si interrompe. Ma ciò avviene raramente.)

(Ho provato a utilizzare un file diverso e rieseguire come sopra e l'ordine di esecuzione è cambiato)

Sei sicuro che fosse a causa del file utilizzato e non solo di una variazione casuale? Come con una pipeline, non c'è davvero modo di essere sicuri in quale ordine eseguire i comandi cmd1 & cmd. Dipende principalmente dallo scheduler del sistema operativo. Ottengo variazioni casuali sul mio sistema.

Perché lo script deve reindirizzare, a un descrittore di file ereditato dalla subshell, una copia del contenuto di un file, comunque?

Sembra che sia così che la shell stessa contenga una copia della descrizione del file che contiene il blocco, anziché solo l' flockutilità che lo tiene. Un blocco creato con flock(2)viene rilasciato quando i descrittori di file che lo hanno sono chiusi.

flockha due modalità, o per prendere un blocco basato su un nome di file, ed eseguire un comando esterno (nel qual caso flockcontiene il descrittore di file aperto richiesto), o per prendere un descrittore di file dall'esterno, quindi un processo esterno è responsabile per la conservazione esso.

Si noti che il contenuto del file non è pertinente qui e non sono state eseguite copie. Il reindirizzamento alla subshell non copia alcun dato in sé, ma apre solo un handle al file.

Perché tenere un blocco esclusivo sul descrittore di file 0 in una shell impedisce a una copia dello stesso script, in esecuzione in una shell diversa, di ottenere un blocco esclusivo sul descrittore di file 0? Le shell non hanno le proprie copie separate dei descrittori di file standard (0, 1 e 2, ovvero STDIN, STDOUT e STDERR)?

Sì, ma il blocco è sul file , non sul descrittore di file. Solo un'istanza aperta del file può contenere il blocco alla volta.


Penso che dovresti essere in grado di fare lo stesso senza la subshell, usando execper aprire un handle per il file di blocco:

$ cat lock.sh
#!/bin/sh

exec 9< "$0"

if ! flock -n -x 9; then
    echo "$$/$1 cannot get flock" 
    exit 0
fi

echo "$$/$1 got the lock"
sleep 2
echo "$$/$1 exit"

$ ./lock.sh bg & ./lock.sh fg ; wait; echo
[1] 11362
11363/fg got the lock
11362/bg cannot get flock
11363/fg exit
[1]+  Done                    ./lock.sh bg

1
L'uso di { }anziché ( )funzionerebbe anche ed eviterebbe la subshell.
R ..

Più in basso nei commenti sul post G +, qualcuno ha anche suggerito all'incirca lo stesso metodo usando exec.
David Z,

@R .., oh, certo. Ma è ancora brutto con le parentesi extra attorno alla sceneggiatura reale.
ilkkachu,

9

Un blocco file è allegato a un file, attraverso una descrizione del file . A un livello elevato, la sequenza di operazioni in un'istanza dello script è:

  1. Aprire il file a cui è allegato il blocco ("il file di blocco").
  2. Blocca il file di blocco.
  3. Fare cose.
  4. Chiudi il file di blocco. Questo rilascia il blocco che è allegato alla descrizione del file creata aprendo un file.

Tenere premuto il blocco impedisce l'esecuzione di un'altra copia dello stesso script perché è ciò che fanno i blocchi. Finché esiste un blocco esclusivo su un file da qualche parte sul sistema, è impossibile creare una seconda istanza dello stesso blocco, anche attraverso una diversa descrizione del file.

L'apertura di un file crea una descrizione del file . Questo è un oggetto kernel che non ha molta visibilità diretta nelle interfacce di programmazione. Si accede indirettamente a una descrizione di file tramite descrittori di file, ma normalmente si pensa che acceda al file (leggendo o scrivendo il suo contenuto o metadati). Un blocco è uno degli attributi che sono una proprietà della descrizione del file anziché di un file o di un descrittore.

All'inizio, quando viene aperto un file, la descrizione del file ha un singolo descrittore di file, ma è possibile creare più descrittori creando un altro descrittore (la dupfamiglia di chiamate di sistema) o creando un sottoprocesso (dopodiché sia ​​il genitore che il il bambino ha accesso alla stessa descrizione del file). Un descrittore di file può essere chiuso esplicitamente o quando il processo in cui si trova muore. Quando viene chiuso l'ultimo descrittore di file allegato a un file, la descrizione del file viene chiusa.

Ecco come la sequenza delle operazioni sopra influisce sulla descrizione del file.

  1. Il reindirizzamento <$0apre il file di script nella subshell, creando una descrizione del file. A questo punto c'è un singolo descrittore di file allegato alla descrizione: descrittore numero 0 nella subshell.
  2. La subshell invoca flocke attende che esca. Mentre il flock è in esecuzione, ci sono due descrittori allegati alla descrizione: numero 0 nella subshell e numero 0 nel processo flock. Quando flock prende il lock, questo imposta una proprietà della descrizione del file. Se un'altra descrizione del file ha già un blocco sul file, flock non può prendere il blocco, poiché è un blocco esclusivo.
  3. La subshell fa cose. Dal momento che ha ancora un descrittore di file aperto sulla descrizione con il blocco, tale descrizione rimane esistente e mantiene il suo blocco poiché nessuno lo rimuove mai.
  4. La subshell muore alla parentesi di chiusura. Ciò chiude l'ultimo descrittore di file nella descrizione del file che ha il blocco, quindi il blocco scompare a questo punto.

Il motivo per cui lo script utilizza un reindirizzamento $0è che il reindirizzamento è l'unico modo per aprire un file nella shell e mantenere attivo un reindirizzamento è l'unico modo per mantenere aperto un descrittore di file. La subshell non legge mai dal suo input standard, deve solo tenerlo aperto. In una lingua che consente l'accesso diretto alla chiamata di apertura e chiusura, è possibile utilizzare

fd = open($0)
flock(fd, LOCK_EX)
do stuff
close(fd)

Puoi effettivamente ottenere la stessa sequenza di operazioni nella shell se esegui il reindirizzamento con l' execintegrato.

exec <$0
flock -n -x 0
# do stuff
exec <&-

Lo script potrebbe utilizzare un descrittore di file diverso se desidera continuare ad accedere all'input standard originale.

exec 3<$0
flock -n -x 0
# do stuff
exec 3<&-

o con una subshell:

(
  flock -n -x 3
  # do stuff
) 3<$0

Il blocco non deve trovarsi nel file di script. Potrebbe essere su qualsiasi file che può essere aperto per la lettura (quindi deve esistere, deve essere un tipo di file che può essere letto come un file normale o una pipe denominata ma non una directory e il processo di script deve avere il permesso di leggerlo). Il file di script ha il vantaggio di essere garantito e leggibile (tranne nel caso limite in cui è stato eliminato esternamente tra il momento in cui lo script è stato invocato e il momento in cui lo script arriva al <$0reindirizzamento).

Finché flockriesce, e lo script si trova su un filesystem in cui i blocchi non sono corretti (alcuni filesystem di rete come NFS possono essere difettosi), non vedo come l'utilizzo di un diverso file di blocco possa consentire una condizione di competizione. Sospetto un errore di manipolazione da parte tua.


C'è una condizione di competizione: non puoi controllare quale istanza dello script ottiene il blocco. Fortunatamente, per quasi tutti gli scopi, non importa.
Mark

4
@Mark C'è una corsa alla serratura, ma non è una condizione di gara. Una condizione di competizione è quando il tempismo può consentire che accada qualcosa di brutto, come due processi che si trovano nella stessa sezione critica allo stesso tempo. Non sapendo quale processo entrerà nella sezione critica è previsto il non determinismo, non è una condizione di razza.
Gilles 'SO- smetti di essere malvagio' il

1
Cordiali saluti, il link nella "descrizione del file" punta alla pagina dell'indice delle specifiche di Open Group piuttosto che alla descrizione specifica del concetto, che è quello che penso tu abbia intenzione di fare. Oppure puoi anche collegare la tua risposta precedente qui e unix.stackexchange.com/a/195164/85039
Sergiy Kolodyazhnyy

5

Il file utilizzato per il blocco non è importante, lo script utilizza $0perché è un file noto per esistere.

L'ordine in cui si ottengono i blocchi sarà più o meno casuale, a seconda della velocità con cui la macchina è in grado di avviare le due attività.

È possibile utilizzare qualsiasi descrittore di file, non necessariamente 0. Il blocco si trova sul file aperto al descrittore di file, non sul descrittore stesso.

( flock -x 9 || exit 1
  echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.