Bagaimana skrip ini memastikan bahwa hanya satu instance dari dirinya yang berjalan?

22

Pada 19 Agustus 2013, Randal L. Schwartz memposting skrip shell ini , yang dimaksudkan untuk memastikan, di Linux, "bahwa hanya satu instance skrip sedang berjalan, tanpa syarat balapan atau harus membersihkan file kunci":

#!/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

Tampaknya berfungsi seperti yang diiklankan:

$ ./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
$

Inilah yang saya mengerti:

  • Script mengarahkan ( <) salinan dari isinya sendiri (yaitu dari $0) ke STDIN (yaitu deskriptor file 0) dari sebuah subkulit.
  • Dalam subkulit, skrip mencoba untuk mendapatkan kunci ( flock -n -x) eksklusif non-pemblokiran pada deskriptor file 0.
    • Jika upaya itu gagal, subshell keluar (dan begitu juga skrip utama, karena tidak ada lagi yang bisa dilakukan).
    • Jika upaya tersebut berhasil, subkulit menjalankan tugas yang diinginkan.

Ini pertanyaan saya:

  • Mengapa skrip perlu diarahkan, ke deskriptor file yang diwarisi oleh subkulit, salinan kontennya sendiri, bukan, katakanlah, isi beberapa file lain? (Saya mencoba mengalihkan dari file yang berbeda dan menjalankan kembali seperti di atas, dan urutan eksekusi berubah: tugas non-latar belakang memperoleh kunci sebelum latar belakang. Jadi, mungkin menggunakan konten file sendiri menghindari kondisi ras; tetapi bagaimana?)
  • Mengapa skrip perlu diarahkan, ke deskriptor file yang diwarisi oleh subkulit, salinan konten file?
  • Mengapa memegang kunci eksklusif pada deskriptor file 0dalam satu shell mencegah salinan script yang sama, berjalan di shell yang berbeda, dari mendapatkan kunci eksklusif pada deskriptor file 0? Jangan kerang memiliki sendiri, salinan yang terpisah mereka dari file deskriptor standar ( 0, 1, dan 2, yaitu STDIN, STDOUT, dan stderr)?
sampablokuper
sumber
Bagaimana proses pengujian Anda saat Anda mencoba percobaan untuk mengarahkan ulang dari file yang berbeda?
Freiheit
1
Saya pikir Anda dapat merujuk tautan ini. stackoverflow.com/questions/185451/…
Deb Paikar

Jawaban:

22

Mengapa skrip perlu diarahkan, ke deskriptor file yang diwarisi oleh subkulit, salinan kontennya sendiri, dan bukannya, katakanlah, konten beberapa file lain?

Anda dapat menggunakan file apa saja, selama semua salinan skrip menggunakan yang sama. Menggunakan $0hanya mengikat kunci ke skrip itu sendiri: Jika Anda menyalin skrip dan memodifikasinya untuk penggunaan lain, Anda tidak perlu membuat nama baru untuk file kunci. Ini nyaman.

Jika skrip dipanggil melalui symlink, kuncinya ada pada file aktual, dan bukan tautannya.

(Tentu saja, jika beberapa proses menjalankan skrip dan memberinya nilai yang dibuat sebagai argumen nol bukannya jalan yang sebenarnya, maka ini rusak. Tapi itu jarang dilakukan.)

(Saya mencoba menggunakan file yang berbeda dan menjalankan kembali seperti di atas, dan urutan eksekusi berubah)

Apakah Anda yakin itu karena file yang digunakan, dan bukan hanya variasi acak? Seperti halnya pipeline, benar-benar tidak ada cara untuk memastikan dalam urutan apa perintah bisa dijalankan cmd1 & cmd. Sebagian besar terserah pada penjadwal OS. Saya mendapatkan variasi acak pada sistem saya.

Mengapa skrip perlu diarahkan, ke deskriptor file yang diwarisi oleh subkulit, salinan konten file?

Sepertinya shell itu sendiri yang menyimpan salinan deskripsi file yang menahan kunci, bukan hanya flockutilitas yang menahannya. Kunci yang dibuat dengan flock(2)dilepaskan ketika deskriptor file yang memilikinya ditutup.

flockmemiliki dua mode, baik untuk mengambil kunci berdasarkan nama file, dan menjalankan perintah eksternal (dalam hal ini flockmemegang deskriptor file terbuka yang diperlukan), atau untuk mengambil file deskriptor dari luar, sehingga proses luar bertanggung jawab untuk memegang saya t.

Perhatikan bahwa konten file tidak relevan di sini, dan tidak ada salinan yang dibuat. Pengalihan ke subkulit tidak menyalin data apa pun di sekitarnya, itu hanya membuka pegangan ke file.

Mengapa memegang kunci eksklusif pada deskriptor file 0 dalam satu shell mencegah salinan skrip yang sama, berjalan di shell yang berbeda, dari mendapatkan kunci eksklusif pada deskriptor file 0? Tidakkah shell memiliki salinan deskriptor file standar mereka sendiri yang terpisah (0, 1, dan 2, yaitu STDIN, STDOUT, dan STDERR)?

Ya, tetapi kuncinya ada di file , bukan deskriptor file. Hanya satu instance file yang dibuka yang dapat menahan kunci pada suatu waktu.


Saya pikir Anda harus dapat melakukan hal yang sama tanpa subkulit, dengan menggunakan execuntuk membuka pegangan ke file kunci:

$ 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
ilkkachu
sumber
1
Menggunakan { }bukannya ( )juga akan bekerja dan menghindari subkulit.
R ..
Lebih jauh ke bawah di komentar pada posting G +, seseorang di sana juga menyarankan menggunakan metode yang kira-kira sama exec.
David Z
@R .., oh, tentu. Tapi itu masih jelek dengan kawat gigi tambahan di sekitar skrip yang sebenarnya.
ilkkachu
9

Kunci file dilampirkan ke file, melalui deskripsi file . Pada tingkat tinggi, urutan operasi dalam satu contoh skrip adalah:

  1. Buka file yang dilampirkan gemboknya (“file kunci”).
  2. Ambil kunci pada file kunci.
  3. Lakukan hal-hal.
  4. Tutup file kunci. Ini melepaskan kunci yang dilampirkan ke deskripsi file yang dibuat dengan membuka file.

Menahan kunci mencegah salinan lain dari skrip yang sama untuk dijalankan karena itulah yang dilakukan kunci. Selama kunci eksklusif pada file ada di suatu tempat di sistem, tidak mungkin membuat instance kedua dari kunci yang sama, bahkan melalui deskripsi file yang berbeda.

Membuka file menciptakan deskripsi file . Ini adalah objek kernel yang tidak memiliki banyak visibilitas langsung dalam antarmuka pemrograman. Anda mengakses deskripsi file secara tidak langsung melalui deskriptor file, tetapi biasanya Anda menganggapnya sebagai mengakses file (membaca atau menulis konten atau metadata). Kunci adalah salah satu atribut yang merupakan properti untuk deskripsi file daripada file atau ke deskriptor.

Pada awalnya, ketika file dibuka, deskripsi file memiliki deskriptor file tunggal, tetapi lebih banyak deskriptor dapat dibuat dengan membuat deskriptor lain ( dup keluarga panggilan sistem) atau dengan membentuk subproses (setelah itu baik induk dan anak memiliki akses ke deskripsi file yang sama). Deskriptor file dapat ditutup secara eksplisit atau ketika prosesnya mati. Ketika deskriptor file terakhir yang dilampirkan ke file ditutup, deskripsi file ditutup.

Begini cara urutan operasi di atas memengaruhi deskripsi file.

  1. Pengalihan <$0membuka file skrip di subkulit, membuat deskripsi file. Pada titik ini ada satu file deskriptor terlampir pada deskripsi: deskriptor nomor 0 di subkulit.
  2. Subshell memanggil flock dan menunggu sampai keluar. Saat kawanan sedang berjalan, ada dua penjelas yang terlampir pada deskripsi: angka 0 di subkulit dan angka 0 dalam proses kawanan. Ketika kawanan mengambil kunci, itu mengatur properti dari deskripsi file. Jika deskripsi file lain sudah memiliki kunci pada file tersebut, kawanan tidak dapat mengambil kunci, karena itu adalah kunci eksklusif.
  3. Subkulit melakukan hal-hal. Karena masih memiliki deskriptor file terbuka pada deskripsi dengan kunci, deskripsi itu tetap ada, dan itu menjaga kunci karena tidak ada yang pernah menghapus kunci.
  4. Subshell mati pada kurung tutup. Ini menutup deskriptor file terakhir pada deskripsi file yang memiliki kunci, sehingga kunci menghilang pada saat ini.

Alasan skrip menggunakan pengalihan dari $0adalah bahwa pengalihan adalah satu-satunya cara untuk membuka file di shell, dan menjaga pengalihan aktif adalah satu-satunya cara untuk menjaga deskriptor file tetap terbuka. Subkulit tidak pernah membaca dari input standar, hanya perlu tetap terbuka. Dalam bahasa yang memberikan akses langsung ke buka dan tutup panggilan, Anda bisa menggunakan

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

Anda benar-benar bisa mendapatkan urutan operasi yang sama di shell jika Anda melakukan pengalihan dengan execbuiltin.

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

Script dapat menggunakan deskriptor file yang berbeda jika ingin tetap mengakses input standar asli.

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

atau dengan subkulit:

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

Kunci tidak harus ada di file skrip. Itu bisa di file apa saja yang bisa dibuka untuk dibaca (jadi harus ada, itu harus tipe file yang bisa dibaca seperti file biasa atau pipa bernama tetapi bukan direktori, dan proses skrip harus memiliki izin untuk membacanya). File skrip memiliki keuntungan bahwa itu dijamin untuk hadir dan dapat dibaca (kecuali dalam kasus tepi di mana itu dihapus secara eksternal antara waktu skrip dipanggil dan waktu skrip sampai ke <$0pengalihan).

Selama flockberhasil, dan skrip berada pada sistem file di mana kunci tidak buggy (beberapa sistem file jaringan seperti NFS mungkin buggy), saya tidak melihat bagaimana menggunakan file kunci yang berbeda dapat memungkinkan kondisi balapan. Saya menduga ada kesalahan manipulasi di pihak Anda.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Ada kondisi balapan: Anda tidak dapat mengontrol yang instance dari script mendapat kunci. Untungnya, untuk hampir semua tujuan, itu tidak masalah.
Tandai
4
@ Mark Ada perlombaan untuk mengunci, tapi itu bukan kondisi balapan. Sebuah kondisi balapan adalah ketika waktu dapat memungkinkan sesuatu yang buruk terjadi, seperti dua proses berada di bagian kritis yang sama pada waktu yang sama. Tidak tahu proses mana yang akan masuk ke bagian kritis yang diharapkan nondeterminisme, itu bukan kondisi balapan.
Gilles 'SO- stop being evil'
1
Hanya FYI, tautan dalam "deskripsi file" menunjuk ke halaman indeks spesifikasi Open Group daripada ke deskripsi spesifik konsep, yang menurut saya ingin Anda lakukan. Atau Anda juga dapat menautkan jawaban lama Anda di sini juga unix.stackexchange.com/a/195164/85039
Sergiy Kolodyazhnyy
5

File yang digunakan untuk mengunci tidak penting, skrip menggunakan $0karena itu file yang diketahui ada.

Urutan penguncian akan lebih atau kurang acak, tergantung pada seberapa cepat mesin Anda dapat memulai dua tugas.

Anda dapat menggunakan deskriptor file apa pun, tidak harus 0. Kunci dipegang pada file yang dibuka untuk deskriptor file, bukan deskriptor itu sendiri.

( flock -x 9 || exit 1
  echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &
Kusalananda
sumber