Mengapa Shell Script Trapping SIGTERM Bekerja Saat Dijalankan Secara Manual, Tapi Tidak Saat Dijalankan melalui launchd?

4

Oke, sederhananya saya memiliki skrip shell yang perlu menunggu sesuatu terjadi, tetapi ia memiliki file kunci dan beberapa proses anak yang perlu saya pastikan dirapikan jika skrip terputus.

Saya telah mencapai ini tanpa masalah dengan menggunakan trapperintah untuk mengatur beberapa tindakan yang sesuai, dan telah menghasilkan skrip yang terlihat seperti ini:

#!/bin/sh
LOG="$0.log"

# Create a lock-file to prevent simultaneous access
lockfile -l 86400 "$LOG.lock" || $(echo 'Locking failed' >&2 && exit 3)

# Create trap for interrupt and cleanup
on_complete() {
    echo $(date +%R)' Ended.' >> "$LOG"
    kill $(jobs -p)
    rm -f "$LOG.lock"
    exit
}
trap 'on_complete 2> /dev/null' SIGTERM SIGINT SIGHUP EXIT

# Do nothing
echo $(date +%R)' Running…' >> "$LOG"
sleep 86400 &
while wait; do sleep 86400 &; done

Ini dapat dijalankan dengan baik di terminal melalui sh Example.sh, dan mengakhiri dengan Ctrl + C, menyebabkannya untuk menghapus file kunci tanpa keributan.

Saya kemudian mencoba membuat launchdpekerjaan untuk skrip ini seperti:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.example</string>
    <key>ProgramArguments</key>
    <array>
        <string>sh</string>
        <string>~/Downloads/Example.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>EnableGlobbing</key>
    <true/>
</dict>
</plist>

Membuat Example.sh dan Example.plist dari ~/Downloadsfolder di atas memungkinkan saya untuk menjalankan launchdpekerjaan melalui launchd load ~/Downloads/Example.plistdan mengakhirinya melalui launchd unload ~/Downloads/Example.plist. Namun, mengakhiri pekerjaan tidak menyebabkan a SIGTERMmencapai skrip, yang malah SIGKILLakan terjadi setelah batas waktu 20 detik.

Jadi yang ingin saya ketahui adalah; mengapa skrip saya tidak terima SIGTERM, dan bagaimana saya bisa memastikannya?

Haravikk
sumber
1
Apakah perilaku ini berlanjut jika Anda menghapus 'sh' dan memanggil skrip secara langsung? Saya mengasumsikan skrip memiliki flag yang dapat dieksekusi ditetapkan.
Graham Miln
1
Daripada 'launchd unload', sudahkah Anda mencoba 'launchctl stop ~ / Downloads / Example.plist'?
audiomason

Jawaban:

5

Masalah utama di sini adalah bahwa Bash biasanya tidak membunuh anak-anak non-bawaannya.

If bash is waiting for a command to complete and receives a signal for which a
trap has been set, the trap will not be executed until the command completes.
When bash is waiting for an asynchronous command  via  the  wait  builtin, the
reception of a signal for which a trap has been set will cause the wait builtin
to return immediately with an exit status greater than 128, immediately after
which  the trap is executed.

Ketika Anda menekan <CTRL>+<C>Anda membunuh skrip shell, yang berperilaku normal - tetapi tidur tetap hidup. Gunakan psuntuk melihat.

Ketika mencoba menghentikan hal-hal secara eksternal, melalui kill, maka Bash seperti di atas. Setelah beberapa periode time-out (saya kira 20 detik) launchdkemudian mengeluarkan sebuah kill -9script yang tidak bisa dijebak.

Solusinya adalah mengeluarkan menunggu setelah tidur, untuk menunjukkan kepada Bash bahwa itu dapat mengganggu dirinya sendiri:

sleep 86400 & wait

Ini akan memungkinkan skrip terputus, tetapi tidur masih akan bertahan. Saya yakin ada cara untuk membunuh anak-anak, tetapi saya tidak repot mencarinya ...

Joe Casadonte
sumber
Terima kasih atas penjelasannya, tetapi penggunaan waittidak membantu (itu yang saya lakukan dalam skrip sebenarnya yang saya coba debug, saya telah mengubah contoh untuk mencocokkan sedikit lebih dekat), jadi saya tidak yakin apa yang sedang terjadi.
Haravikk
Ternyata pada Yosemite inilah jawaban yang benar; agen peluncuran atau peluncuran daemon yang sedang tidur (asinkron) dengan jebakan yang sesuai (harus SIGINT, tidak INT) akan menerima sinyal sebelum diturunkan. Tentu saja ini tidak baik untuk Mavericks, Mountain Lion dll., Tetapi ini bagus bahwa ini akhirnya berfungsi sebagaimana mestinya, jadi saya menandai ini adalah jawaban yang benar, tetapi mungkin perlu diedit karena hanya berfungsi dengan benar di bawah 10.10 .
Haravikk
4

Menyadari Anda baru saja berbagi dengan kami sebuah fragmen kode dan pada dasarnya tidak jelas apa yang ingin dicapai oleh daemon selain melakukan beberapa tindakan setiap detik. Jadi saya akan membuat beberapa asumsi hanya berdasarkan apa yang Anda tulis.

  1. Sepertinya Anda menggunakan lockfile untuk mencegah peluncuran duplikat.
  2. Tampaknya Anda perlu jebakan untuk membersihkan file kunci yang digunakan untuk mengimplementasikan tes Anda untuk memastikan singularitas.
  3. Selain itu tampaknya deamon Anda sedang melakukan sleep loop untuk bangun secara berkala dan melakukan beberapa tindakan. (Hanya tidur lebih banyak, dalam contoh Anda.)

Ini semua adalah masalah yang launchd dimaksudkan untuk diselesaikan dengan cara yang lebih baik di bawah Darwin (dan karenanya OS X).

Adapun pertanyaan (s) dengan membongkar dan SIGTERM, khususnya, ketika Anda unloadlaunchdeamon Anda dikirim SIGKILL bukan SIGTERM. Jika Anda hanya ingin menghentikan pekerjaan atau mengirimnya SIGTERM kemudian gunakan stopsebagai gantinya unload.

Jika Anda ingin SIGTERM dikirim, unloadAnda mungkin perlu mengatur EnableTransactions. Demikian juga jika Anda memiliki tugas pembersihan dan Anda ingin deamon Anda menerima sinyal untuk pembersihan dan SIGTERM maka Anda harus menetapkan EnableTransactionssebagai bagian dari launchd plist untuk skrip Anda. <key>EnableTransactions</key><true/>. Ini dijelaskan dalam dokumen di https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html

Tetapi tiga mekanisme di atas tidak perlu diberikan launchd ...

Di bawah Darwin / OS X menggunakan launchdaemons, metode yang tepat untuk menerapkan sleep loop daemon adalah digunakan StartIntervaluntuk berjalan pada interval atau StartCalendarIntervaluntuk menjalankan berdasarkan pada waktu tertentu. Menggunakan StartCalendarIntervaltambahan juga memberikan keuntungan bahwa ketika sistem tertidur itu akan mengeksekusi waktu interval yang terjawab alih-alih harus menunggu interval berikutnya, dan umumnya apa yang Anda inginkan dalam situasi ini. Jika Anda memiliki pekerjaan yang Anda hanya ingin tetap dipanggil, pertimbangkan juga untuk menggunakan KeepAlivesebagai bagian dari daftar.

Jadi sepertinya - dari contoh kode yang Anda berikan - Anda hanya ingin mengeksekusi sesuatu setiap 86400 detik. Jika hal ini terjadi maka launchd memiliki mekanisme untuk melakukan hal ini yang harus Anda gunakan sebagai gantinya dan meniadakan kebutuhan untuk file kunci dan perangkap sekaligus karena launchd dirancang untuk menangani semua ini untuk Anda secara otomatis. Mekanisme itu StartIntervaldan ketika ditetapkan akan meluncurkan deamon Anda setiap N detik. Launchd juga memastikan belum meluncurkan banyak salinan daemon Anda.

Mekanisme ini dijelaskan dalam dokumen launchd di https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html di mana ia menyatakan:

StartInterval <integer>
This optional key causes the job to be started every N seconds.  If the system is
asleep, the job will be started the next time the computer wakes up.  If multiple
intervals transpire before the computer is woken, those events will be coalesced 
into one event upon wake from sleep.

Jadi skrip Anda yang ter-Darwin-kan ~/Downloads/Example.shakan terlihat sangat sederhana sekarang seperti ini:

#!/bin/sh
echo $(date +%R)' Running…' # or whatever it is you wanted to do on the interval

Dan daftar Anda akan terlihat seperti ini:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.example</string>
    <key>ProgramArguments</key>
    <array>
        <string>sh</string>
        <string>~/Downloads/Example.sh</string>
    </array>
    <key>EnableGlobbing</key>
    <true/>
    <key>StartInterval</key>
    <integer>86400</integer>
    <key>StandardOutPath</key>
    <string>/mypathtolog/myjob.log</string>
    <key>StandardErrorPath</key>
    <string>/mypathtolog/myjob.log</string>
</dict>
</plist>

Catatan Saya juga telah menyesuaikan ini untuk mengatur file logging di sini dengan cara seperti Darwin / launchd daripada dalam script itu sendiri. (Tentu saja Anda bisa menghapusnya dan menanganinya dalam skrip Anda, tetapi itu tidak perlu diberikan launchd.)

Saya perhatikan bahwa Anda juga dapat mengimplementasikan ini menggunakan Programseperti:

<key>Program</key>
<string>sh</string>
<key>ProgramArguments</key>
<array>
    <string>~/Downloads/Example.sh</string>
</array>

Anda juga dapat menemukan http://launchd.info referensi yang berguna bersama dengan dokumen Apple untuk bagaimana launchd beroperasi di https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ Introduction.html

Informasi tentang daemon dijalankan secara berkala dapat ditemukan di https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html#//apple_ref/doc/uid/10000172i-CH1-S2

KolonelMode
sumber
Maaf, saya seharusnya mengatakan bahwa sebenarnya skrip saya sedang menunggu logout / shutdown, saat itulah launchdakan mengirim SIGINTke semua agen yang menjalankan (atau lebih tepatnya, harus). EnableTransactionstrue tidak akan berfungsi karena skrip shell tidak dapat memanggil perintah vproc, dan disetel ke false seharusnya menjadi default. Saya sudah mencoba memasukkannya sebagai salah untuk memastikan tetapi tampaknya tidak membantu.
Haravikk
OK, itu situasi yang sama sekali berbeda dari apa yang tampaknya berusaha dilakukan oleh skrip Anda, dan di mana solusi yang Anda buat adalah solusi kompleks untuk masalah non-sepele. Ada cara yang benar-benar lebih baik untuk menunggu logout dan shutdown di OS X. Juga EnableTransactionsberfungsi untuk sinyal yang sesuai terlepas dari apakah proses tersebut benar-benar menggunakan vproc atau tidak, bahwa komentar dokumentasi pada dasarnya adalah untuk aplikasi yang sebenarnya, tetapi secara fungsional sama saja.
KolonelMode
Maaf, itu disimpan sebelum saya selesai.
KolonelMode
Anda dapat membuat LaunchAgent menunggu untuk keluar atau menggunakan logouthook ( sudo defaults write com.apple.loginwindow LogoutHook /Users/Shared/logoutHook.sh).
KolonelMode
Apakah kait logout memerlukan sudo atau dapatkah itu dilakukan per pengguna? Cara yang benar untuk menonton logout atau tidak, saya masih bingung mengapa saya tidak mendapatkan SIGINTskrip saya apa adanya; apakah Anda dapat mereproduksi masalah dengan menjalankan kode contoh yang saya berikan? Pengaturan untuk EnableTransactionstampaknya tidak membuat perbedaan bagi saya.
Haravikk