Az erőforrások kisajátítása minden programnyelven problémát jelent. Ilyen-olyan megoldások vannak rá. Vannak, ahol csak indirekt módon valósítható meg. Vagy maga a nyelv nem támogatja, vagy az OS (sem). A probléma lényege, hogyan lehet biztos egy bash szkript, hogy ő egyedül művel valamit a rendszerben. A kérdés viszonylag egyszerű, de valóban jó megoldást nem találtam rá. Bash-ban csak kooperatív megoldást tudtam összetákolni.
A legegyszerűbb (problémás) megoldás:
if [ ! -f "$lock_file" ]; then
Ezzel sajna több probléma is vagyon:
>"$lockfile"
#Kritikus_resz
rm -f "$lockfile"
fi
- Ha elhal a szkript a kritikus részben, akkor a lock_file ott marad, tehát addig nem fogjuk tudni futtatni az alkalmazást, amíg valaki észre nem veszi, hogy az alkalmazás már halott és kézzel le nem törli a lock file-t. Ezzel azt lehet kezdeni, hogy a lock file-ba betesszük a procesz PID-jét és a mások program "kill -0 <PID>" utasítással meg tudja nézni, hogy fut-e. Ennél kicsit pontosabb, ha a /proc/<PID>/cmdline-t vizsgáljuk. Már ha van /proc filesystem. Persze lehet "ps -ef|grep...|awk....|wc..." kígyókat is alkalmazni, de tök felesleges! Akinél pedig meglátom, hogy grep, sed mellett awk-t is használ, ráverek a kezére! Egy awk hívással lekezelhető az egész! Hasonló sors vár a "cat file|grep valami" használókra is!
- "Race condition" helyzet van, azaz tegyük fel, hogy egyszerre indul két szkript. Mindegyik azt látja, hogy nincs lock_file és mind a kettő megcsinálja a lock_file-t és lefuttatja a kritikus részt. Pont ezt szeretnénk elkerülni.
Az első problémát a fontos események csapdázásával (trap) oldhatjuk meg, a másikat pedig valamilyen olyan hívással, ami egyszerre csinál valamit és elbukik, ha nem tudja már megcsinálni (azaz atomi művelet. Atomi a démokritoszi értelemben! És ne jöjjön senki a kvarkokkal!). Ezekre példa: mv file file2;, ln file file2;, ln -s file file2, (set -o noclobber; >lock_file), mkfifo pipe. Ha nem szeretnénk semmilyen más infót beleírni a lock file-ba (mondjuk a PID-et, ami néha hasznos lehet), akkor én az ln-t (hard link) használnám, mert akkor csak egy bejegyzés keletkezik a directory-ba, de egyéb terület nem kell, hogy lefoglalódjon és megspórolunk pár lemezműveletet is (persze fontos, hogy a link ugyanarra a partícióra kerüljön, mint az eredeti linkelendő file).
lock="$0.lock"
Néhol az INT és TERM-et is trap-polják, de tesztjeim szerint Cygwin és Linux alatt is mindig jól lefut az EXIT (pl. Ctrl+C, kill -HUP, kill -TERM). Még akkor is, ha a szülő bash-t lövöm le. Az ln kimenete a /dev/null-ba irányítandó, ha nem szeretnénk a hibaüzeneteket látni.
if ln "$0" "$lock"; then
trap 'rm -f "$pipe"; exit $?;' EXIT
kritikus_dolgok
rm -f "$lock"
trap - EXIT
else
echo "Cannot get locked"
fi
Persze ez sem tökéletes, mert az ln és a trap között még marad egy kis esély, hogy mondjuk egy megszakítás beszaladjon és akkor a lock fent marad, mert a trap nem hajtódik végre... Ha a trap-et korábban csináljuk, mint az if-fet és a második példány vagyunk, akkor egy jól irányzott ctrl+C-vel leszedhetjük a lock file-t az else ágban, mielőtt a trap-et leállíthatnánk. Szóval valami olyan kellene, ami a trap-et és a lock-ot egyszerre állítja. Ilyet nem találtam...
Esetleg ezt lehetne még, de szerintem ez sem nevezhető "atomi" lépésnek, inkább csak kódfésülés:
if ln "$0" "$lock" && trap 'rm -f "$lock"; exit $?;' EXIT; then
kritikus_dolgok
rm -f "$lock"
trap - EXIT
fi
Előfordul, hogy a másodpéldányoknak kommunikálniuk kell az első példánnyal. Ekkor a lock file lehet egy pipe (FIFO) file is, amit "mknod pipe p", vagy "mkfifo pipe" paranccsal hozhatunk létre. Sajnos cygwin alatt egyes üzenetek elvesznek, ha fifo-ba írunk. Ezt nem értem, hogy miért van... Sok helyen azt olvastam, hogy cygwin alatt nem megy rendesen a fifo. Akkor sima file-ra váltok, hogy Linux és Cygwin alatt is menjen a dolog:
prog=${0##*/} # get basename
queue="/tmp/$prog.queue"
echo "PID: $$, PPID: $PPID"
if (set -o noclobber; >"$queue" ) 2>/dev/null && trap 'rm -f "$queue"; exit $?;' EXIT; then
# Kritikus resz
echo **EN** $$ $(date) BBB CCC DDD >"$queue"
exec 10<"$queue" # 3 elegendo lenne 10 helyett
while read -a a -u 10; do
echo Olvasva $(date) === ${a[*]}
sleep 5; # Valami fontos
done
exit 0
else
#Nem kritikus
echo $$ $(date) BBB CCC DDD >>"$queue"
fi
Persze a cső nevébe tehetünk felhasználó nevet, egyedi ID-t (pl. PID), vagy bármi mást, ha többen is használnák. Meg persze az eredeti szkriptet is hard linkelhetjük több néven, ami szintén egy szép unix-os megoldás...
Legyünk kritikusak minden nap!
+jegyzések