Cegah pekerjaan duplikat cron berjalan

92

Saya telah menjadwalkan pekerjaan cron untuk dijalankan setiap menit, tetapi kadang-kadang skrip membutuhkan lebih dari satu menit untuk menyelesaikannya dan saya tidak ingin pekerjaan mulai "menumpuk" satu sama lain. Saya kira ini adalah masalah konkurensi - yaitu eksekusi skrip harus saling eksklusif.

Untuk mengatasi masalah tersebut saya membuat skrip mencari keberadaan file tertentu (" lockfile.txt ") dan keluar jika ada atau touchtidak. Tapi ini semaphore yang cukup buruk! Apakah ada praktik terbaik yang harus saya ketahui? Haruskah saya menulis daemon saja?

Tom
sumber

Jawaban:

118

Ada beberapa program yang mengotomatiskan fitur ini, menghilangkan gangguan dan potensi bug dari melakukan ini sendiri, dan menghindari masalah kunci basi dengan menggunakan kawanan di belakang layar, (yang merupakan risiko jika Anda hanya menggunakan sentuhan) . Saya telah menggunakan lockrundan lckdodi masa lalu, tetapi sekarang ada flock(1) (dalam versi util-linux yang agak baru) yang sangat bagus. Ini sangat mudah digunakan:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job
womble
sumber
2
lckdo akan dihapus dari moreutils, sekarang kawanan (1) ada di util-linux. Dan paket itu pada dasarnya wajib dalam sistem Linux, jadi Anda harus bisa mengandalkan kehadirannya. Untuk penggunaan, lihat di bawah.
jldugger
Ya, kawanan sekarang pilihan pilihan saya. Saya bahkan akan memperbarui jawaban saya yang sesuai.
womble
Adakah yang tahu perbedaan antara flock -n file commanddan flock -n file -c command?
Nanne
2
@Nanne, saya harus memeriksa kode untuk memastikan, tetapi tebakan saya adalah -cmenjalankan perintah yang ditentukan melalui shell (sesuai halaman manual), sedangkan "telanjang" (non- -c) bentuk hanya execs perintah yang diberikan . Meletakkan sesuatu melalui shell memungkinkan Anda untuk melakukan hal-hal seperti shell (seperti menjalankan beberapa perintah yang dipisahkan dengan ;atau &&), tetapi juga membuka Anda untuk melakukan serangan ekspansi shell jika Anda menggunakan input yang tidak terpercaya.
womble
1
Itu adalah argumen terhadap perintah (hipotetis) frequent_cron_jobyang mencoba menunjukkan bahwa perintah itu dijalankan setiap menit. Saya telah menghapusnya karena tidak ada yang berguna, dan menyebabkan kebingungan (milik Anda, jika tidak ada orang lain selama ini).
womble
28

Cara terbaik di shell adalah menggunakan flock (1)

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock
Philip Reynolds
sumber
1
Saya tidak bisa tidak memilih penggunaan rumit pengalihan fd. Itu terlalu luar biasa mengagumkan.
womble
1
Tidak mengurai bagi saya di Bash atau zsh, perlu menghilangkan ruang antara 99dan >jadi99> /...
Kyle Brandt
2
@ Javier: Bukan berarti itu tidak rumit dan misterius, hanya saja itu didokumentasikan , rumit, dan misterius.
womble
1
apa yang akan terjadi jika Anda me-restart saat ini sedang berjalan atau membuat proses terbunuh entah bagaimana? Apakah akan terkunci selamanya?
Alex R
5
Saya mengerti struktur ini menciptakan kunci eksklusif tetapi saya tidak mengerti mekanisme bagaimana ini dilakukan. Apa fungsi '99' dalam jawaban ini? Adakah yang mau menjelaskan ini? Terima kasih!
Asciiom
22

Sebenarnya, flock -ndapat digunakan sebagai ganti lckdo*, jadi Anda akan menggunakan kode dari pengembang kernel.

Berdasarkan contoh womble , Anda akan menulis sesuatu seperti:

* * * * * flock -n /some/lockfile command_to_run_every_minute

BTW, melihat kode, semua flock, lockrundan lckdomelakukan hal yang sama persis, jadi hanya masalah yang paling mudah tersedia untuk Anda.

Amir
sumber
2

Anda dapat menggunakan file kunci. Buat file ini saat skrip dimulai dan hapus ketika skrip selesai. Script, sebelum menjalankan rutinitas utamanya, harus memeriksa apakah file kunci ada dan melanjutkannya.

Lockfiles digunakan oleh skrip init dan oleh banyak aplikasi dan utilitas lain dalam sistem Unix.

Lahir untuk berkendara
sumber
1
ini adalah satu - satunya cara yang pernah saya lihat diterapkan, secara pribadi. Saya gunakan pada sesuai saran pengelola sebagai cermin untuk proyek OSS
warren
2

Anda belum menentukan apakah Anda ingin skrip menunggu proses sebelumnya selesai atau tidak. Dengan "Saya tidak ingin pekerjaan mulai" menumpuk "satu sama lain", saya kira Anda menyiratkan bahwa Anda ingin skrip keluar jika sudah berjalan,

Jadi, jika Anda tidak ingin bergantung pada lckdo atau sejenisnya, Anda dapat melakukan ini:


PIDFILE=/tmp/`basename $0`.pid

if [ -f $PIDFILE ]; then
  if ps -p `cat $PIDFILE` > /dev/null 2>&1; then
      echo "$0 already running!"
      exit
  fi
fi
echo $$ > $PIDFILE

trap 'rm -f "$PIDFILE" >/dev/null 2>&1' EXIT HUP KILL INT QUIT TERM

# do the work

Aleksandar Ivanisevic
sumber
Terima kasih contoh Anda sangat membantu - Saya ingin skrip keluar jika sudah berjalan. Terima kasih telah menyebutkan ickdo - sepertinya berhasil.
Tom
FWIW: Saya suka solusi ini karena bisa dimasukkan dalam skrip, jadi pengunciannya bekerja terlepas dari bagaimana skrip dipanggil.
David G
1

Ini mungkin juga pertanda bahwa Anda melakukan hal yang salah. Jika pekerjaan Anda berjalan sedekat itu dan sesering itu, mungkin Anda harus mempertimbangkan untuk tidak mengaturnya dan menjadikannya program gaya daemon.


sumber
3
Saya sungguh tidak setuju dengan ini. Jika Anda memiliki sesuatu yang perlu dijalankan secara berkala, menjadikannya daemon adalah solusi "palu godam untuk kacang". Menggunakan lockfile untuk mencegah kecelakaan adalah solusi yang sangat masuk akal saya tidak pernah punya masalah menggunakan.
womble
@ womble saya setuju; tapi saya suka menghancurkan kacang dengan palu godam! :-)
wzzrd
1

Daemon cron Anda seharusnya tidak menjalankan pekerjaan jika instance sebelumnya masih berjalan. Saya adalah pengembang dari satu cron daemon dcron , dan kami secara khusus mencoba untuk mencegahnya. Saya tidak tahu bagaimana Vixie cron atau daemon lain menangani ini.

dubiousjim
sumber
1

Saya akan merekomendasikan menggunakan perintah run-one - jauh lebih sederhana daripada berurusan dengan kunci. Dari dokumen:

run-one adalah skrip wrapper yang menjalankan tidak lebih dari satu instance unik dari beberapa perintah dengan serangkaian argumen unik. Ini sering berguna dengan cronjobs, ketika Anda ingin tidak lebih dari satu salinan dijalankan pada satu waktu.

run-this-one persis seperti run-one, kecuali ia akan menggunakan pgrep dan kill untuk menemukan dan membunuh proses yang berjalan yang dimiliki oleh pengguna dan mencocokkan perintah target dan argumen. Perhatikan bahwa run-this-one akan memblokir ketika mencoba untuk mematikan proses pencocokan, sampai semua proses pencocokan mati.

run-one-constant beroperasi persis seperti run-one kecuali bahwa ia respawn "PERINTAH [ARGS]" setiap kali PERINTAH keluar (nol atau non-nol).

keep-one-running adalah alias untuk run-one-constant.

run-one-hingga-success beroperasi persis seperti run-one-constant kecuali ia menjalankan "COMMAND [ARGS]" hingga COMMAND keluar dengan sukses (yaitu, keluar dari nol).

run-satu-hingga-kegagalan beroperasi persis seperti run-satu-terus-menerus kecuali menjalankan "PERINTAH [ARGS]" sampai PERINTAH keluar dengan kegagalan (yaitu, keluar tidak nol).

Yurik
sumber
1

Sekarang setelah systemd keluar, ada mekanisme penjadwalan lain pada sistem Linux:

SEBUAH systemd.timer

Di /etc/systemd/system/myjob.serviceatau ~/.config/systemd/user/myjob.service:

[Service]
ExecStart=/usr/local/bin/myjob

Di /etc/systemd/system/myjob.timeratau ~/.config/systemd/user/myjob.timer:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

Jika unit layanan sudah diaktifkan ketika timer berikutnya diaktifkan, maka instance lain dari layanan tidak akan dimulai.

Alternatif, yang memulai pekerjaan sekali saat boot dan satu menit setelah setiap proses selesai:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target
Amir
sumber
0

Saya telah membuat satu toples untuk menyelesaikan masalah seperti duplikat yang sedang berjalan bisa jadi java atau shell cron. Cukup berikan nama cron di Duplicates.CloseSessions ("Demo.jar") ini akan mencari dan membunuh pid pid yang ada untuk cron ini kecuali yang sekarang. Saya telah menerapkan metode untuk melakukan hal ini. String proname = ManagementFactory.getRuntimeMXBean (). GetName (); String pid = proname.split ("@") [0]; System.out.println ("PID Saat Ini:" + pid);

            Process proc = Runtime.getRuntime().exec(new String[]{"bash","-c"," ps aux | grep "+cronname+" | awk '{print $2}' "});

            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String s = null;
            String killid="";

            while ((s = stdInput.readLine()) != null ) {                                        
                if(s.equals(pid)==false)
                {
                    killid=killid+s+" ";    
                }
            }

Dan kemudian bunuh string killid dengan perintah shell lagi

Sachin Patil
sumber
Saya tidak berpikir ini benar-benar menjawab pertanyaan.
kasperd
0

@ Philip Reynolds menjawab akan mulai mengeksekusi kode setelah 5s menunggu waktu lagi tanpa mendapatkan kunci. Mengikuti Flock sepertinya tidak berfungsi, saya memodifikasi @Philip Reynolds menjawab

(
  flock -w 5 -x 99 || exit 1
  ## Do your stuff here
) 99>/path/to/my.lock

sehingga kode tidak akan pernah dieksekusi secara bersamaan. Alih-alih setelah 5 detik menunggu proses akan keluar dengan 1 jika tidak mendapatkan kunci saat itu.

pengguna__42
sumber