Teruskan SIGTERM ke anak di Bash

86

Saya memiliki skrip Bash, yang terlihat mirip dengan ini:

#!/bin/bash
echo "Doing some initial work....";
/bin/start/main/server --nodaemon

Sekarang jika bash shell yang menjalankan skrip menerima sinyal SIGTERM, ia juga harus mengirim SIGTERM ke server yang berjalan (yang memblokir, jadi tidak ada perangkap yang mungkin). Apakah itu mungkin?

Lorenz
sumber

Jawaban:

91

Mencoba:

#!/bin/bash 

_term() { 
  echo "Caught SIGTERM signal!" 
  kill -TERM "$child" 2>/dev/null
}

trap _term SIGTERM

echo "Doing some initial work...";
/bin/start/main/server --nodaemon &

child=$! 
wait "$child"

Biasanya, bashakan mengabaikan sinyal apa pun saat proses anak dieksekusi. Memulai server dengan &latar belakang akan ke sistem kontrol pekerjaan shell, dengan $!memegang PID server (untuk digunakan dengan waitdan kill). Panggilan waitkemudian akan menunggu pekerjaan dengan PID yang ditentukan (server) selesai, atau untuk sinyal yang akan dipecat .

Ketika shell menerima SIGTERM(atau server keluar secara independen), waitpanggilan akan kembali (keluar dengan kode keluar server, atau dengan nomor sinyal + 128 jika sinyal diterima). Setelah itu, jika shell menerima SIGTERM, ia akan memanggil _termfungsi yang ditentukan sebagai pengendali perangkap SIGTERM sebelum keluar (di mana kami melakukan pembersihan dan menyebarkan sinyal secara manual ke proses server menggunakan kill).

cuonglm
sumber
Kelihatan bagus! Saya akan mencobanya dan merespons ketika saya mengujinya.
Lorenz
7
Tapi exec mengganti shell dengan program yang diberikan , saya tidak jelas mengapa waitpanggilan selanjutnya diperlukan?
iruvar
5
Saya pikir poin 1_CR valid. Baik Anda hanya menggunakan exec /bin/start/main/server --nodaemon(dalam hal ini proses shell diganti dengan proses server dan Anda tidak perlu menyebarkan sinyal apa pun) atau Anda gunakan /bin/start/main/server --nodaemon &, tetapi kemudian exectidak benar-benar bermakna.
Andreas Veithen
2
Jika Anda ingin skrip shell Anda berhenti hanya setelah anak dihentikan, maka dalam _term()fungsi Anda harus wait "$child"lagi. Ini mungkin diperlukan jika Anda memiliki proses pengawasan lain menunggu skrip shell mati sebelum memulai kembali, atau jika Anda juga terjebak EXITuntuk melakukan pembersihan dan perlu menjalankannya hanya setelah proses anak selesai.
LeoRochael
1
@AlexanderMills Baca jawaban lain. Entah yang Anda cari exec, atau Anda ingin mengatur jebakan .
Stuart P. Bentley
78

Bash tidak meneruskan sinyal seperti SIGTERM untuk memprosesnya saat ini sedang menunggu. Jika Anda ingin mengakhiri skrip Anda dengan memisahkan ke server Anda (memungkinkannya untuk menangani sinyal dan hal lainnya, seolah-olah Anda telah memulai server secara langsung), Anda harus menggunakan exec, yang akan menggantikan shell dengan proses yang sedang dibuka :

#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon

Jika Anda perlu menjaga shell sekitar untuk beberapa alasan (mis. Anda perlu melakukan beberapa pembersihan setelah server berakhir), Anda harus menggunakan kombinasi trap, waitdan kill. Lihat jawaban SensorSmith .

Stuart P. Bentley
sumber
Ini jawaban yang benar! Jauh lebih ringkas dan alamat asli OP bertanya persis
BrDaHa
20

Andreas Veithen menunjukkan bahwa jika Anda tidak perlu kembali dari panggilan (seperti dalam contoh OP) cukup menelepon melalui execperintah sudah cukup ( jawaban @Stuart P. Bentley ). Kalau tidak, "tradisional" trap 'kill $CHILDPID' TERM(jawaban @ cuonglm) adalah awal, tetapi waitpanggilan benar-benar kembali setelah jebakan pawang berjalan yang masih bisa sebelum proses anak benar-benar keluar. Jadi panggilan "ekstra" waitdisarankan ( jawaban @ user1463361 ).

Walaupun ini merupakan peningkatan namun masih memiliki kondisi balapan yang berarti bahwa proses tersebut mungkin tidak pernah keluar (kecuali pemberi sinyal mencoba mengirim sinyal TERM). Jendela kerentanan adalah antara mendaftarkan penangan perangkap dan merekam PID anak.

Berikut ini menghilangkan kerentanan itu (dikemas dalam fungsi untuk digunakan kembali).

prep_term()
{
    unset term_child_pid
    unset term_kill_needed
    trap 'handle_term' TERM INT
}

handle_term()
{
    if [ "${term_child_pid}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null
    else
        term_kill_needed="yes"
    fi
}

wait_term()
{
    term_child_pid=$!
    if [ "${term_kill_needed}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null 
    fi
    wait ${term_child_pid}
    trap - TERM INT
    wait ${term_child_pid}
}

# EXAMPLE USAGE
prep_term
/bin/something &
wait_term
SensorSmith
sumber
2
Pekerjaan luar biasa - Saya telah memperbarui tautan dalam jawaban saya untuk menunjukkan di sini (di atas ini menjadi solusi yang lebih komprehensif, saya masih agak jengkel bahwa StackExchange UI tidak memuji saya dalam jawaban cuonglm untuk memperbaiki skrip untuk benar-benar melakukan apa yang seharusnya dan menulis hampir semua teks penjelasan setelah OP yang bahkan tidak mengerti membuat beberapa pengeditan ulang kecil).
Stuart P. Bentley
2
@ StuartP.Bentley, terima kasih. Saya adalah terkejut perakitan ini diperlukan dua (tidak diterima) jawaban dan referensi eksternal, dan kemudian saya harus lari ke kondisi race. Saya akan memutakhirkan referensi saya ke tautan sebagai tambahan sedikit pujian yang dapat saya berikan.
SensorSmith
3

Solusi yang diberikan tidak berfungsi untuk saya karena prosesnya dihancurkan sebelum perintah tunggu benar-benar selesai. Saya menemukan artikel itu http://veithen.github.io/2014/11/16/sigterm-propagation.html , potongan terakhir berfungsi dengan baik dalam kasus aplikasi saya mulai di OpenShift dengan pelari kustom sh. Script sh diperlukan karena saya harus memiliki kemampuan untuk mendapatkan dump thread yang tidak mungkin dalam kasus PID proses Java adalah 1.

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
pengguna1463361
sumber