60.13. Zabezpečení skriptu před vícenásobným spuštěním

Odkazy:

U některých programů potřebujeme zajistit aby nedošlo k jejich vícenásobnému paralelnímu spuštění. Můžet to být například proto, že pracují s jedinečnými zdroji.

Dříve jsem používal pro ochranu před vícenásobným spuštěním externí program lock. Bohužel už si nevzpomenu na název balíčku, jehož byl tento program součástí. Protože tento program není ve standardní istalaci, hledal jsem řešení jen s pomocí samotného bash. Delší dobu jsem používal jednoduchou metodu založenou na zámkovém souboru. Tuto metodu ilustruje následující ukázka. Zámkový soubor se používá také pro uložení čísla procesu. Princip je takovýto. Jestliže existuje zámkový soubor, tak již beží jiná instance programu. Ukončíme tedy skript. Jestliže zámkový soubor neexistuje, pak žádná jiná instance programu nebeží a můžeme tedy zamknou zámek pro sebe a označit si do něj číslo aktuálního procesu. Po ukončení programu zase musíme zámek odstranit, což realizuji buďto příkazem rm $PIDFILE na konci programu, nebo definuji trap jako v ukázce.

declare -r PIDFILE=/var/run/${0##*/}

if [[ -f $PIDFILE ]]; then
    echo "${0##*/} is locked so exitting." >&2
    exit
else
    echo $$ >$PIDFILE
    trap 'rm -r $PIDFILE' 0
fi

Tento přístup má zásadní problém. Testování existence souboru a jeho vytváření jsou dvě operace jenž probíhají v čase po sobě. Teoreticky může nastat situace kdy je program spuštěn dvakrát ve stejném čase. Ve stejném čase budou obě instance testovat přítomnost souboru a obě projdou a budou pokračovat dál v domění, že je vše v pořádku. Velmi záleží na tom, jestli je reálná pravděpodobnost, že dojde k současnému vícenásobnému spuštění programu. Pokud ne, například když spouštíme program z cronu, není pro nás tato race condition tak nebezpečná.

Pokud nám to nestačí můžeme s výhodou použít atomické operace mkdir $LOCKDIR. Na tento způsob jsem narazil při pročítání Lock your script (against parallel run). Zde uvádím jen velmi zjednodušenou verzi.

declare -r LOCKDIR=/var/run/${0##*/}.d
declare -r PIDFILE=$LOCKDIR/pid

if mkdir $LOCKDIR; then
    echo $$ >$PIDFILE
    trap 'rm -rf $LOCKDIR' 0
else
    exit
fi

Další možné způsby jsou v podstatě variace na předchozí. Vždy potřebujeme kód s if kde v podmínce je atomická zamykací operace.

if lock; then
    # locked
else
    # Another instance running
    exit
fi