Cara menangani sinyal SIGKILL dengan baik di Java

113

Bagaimana Anda menangani pembersihan ketika program menerima sinyal mematikan?

Misalnya, ada aplikasi yang saya sambungkan yang menginginkan aplikasi pihak ketiga (aplikasi saya) mengirim finishperintah saat keluar. Apa ucapan terbaik untuk mengirim finishperintah itu ketika aplikasi saya dihancurkan dengan kill -9?

edit 1: kill -9 tidak dapat ditangkap. Terima kasih teman-teman telah mengoreksi saya.

sunting 2: Saya kira kasus ini akan terjadi ketika satu panggilan hanya membunuh yang sama dengan ctrl-c

Begui
sumber
44
kill -9bagi saya berarti: "Pergilah, proses busuk, pergi bersamamu!", di mana proses itu akan berhenti. Segera.
ZoogieZork
11
Pada sebagian besar * nix yang saya sadari, kill -9 tidak dapat dicegat dan ditangani dengan anggun oleh program apa pun tidak peduli bahasa apa yang tertulis di dalamnya.
Presiden James K. Polk
2
@Begui: selain komentar dan jawaban orang lain, JIKA Un x OS Anda tidak langsung mematikan * DAN MENCAPAI SEMUA SUMBER DAYA yang digunakan oleh program yang sedang di- kill -9 , yah ... OS rusak.
SyntaxT3rr0r
1
Tentang perintah kill -9, halaman manual mengatakan lebih tepatnya: "9 KILL (non-catchable, non-ignorable kill)". SIGKILL merupakan sinyal yang ditangani oleh OS, bukan aplikasi.
pengguna1338062
4
Hanya saja killtidak sama dengan Ctrl-C, karena killtanpa menentukan sinyal mana yang akan dikirim akan mengirim SIGTERM, sedangkan Ctrl-C mengirim SIGINT.
alesguzik

Jawaban:

136

Tidak mungkin program apa pun, dalam bahasa apa pun, menangani SIGKILL. Jadi, selalu mungkin untuk menghentikan program, meskipun program itu buggy atau jahat. Tetapi SIGKILL bukan satu-satunya cara untuk menghentikan program. Cara lainnya adalah dengan menggunakan SIGTERM. Program dapat menangani sinyal itu. Program harus menangani sinyal dengan melakukan shutdown yang terkontrol, tetapi cepat. Ketika komputer dimatikan, tahap terakhir dari proses mematikan mengirimkan setiap proses yang tersisa sebuah SIGTERM, memberikan proses tersebut beberapa detik waktu tenggang, kemudian mengirimkannya sebuah SIGKILL.

Cara untuk menangani hal ini untuk apa pun lainnya selain kill -9akan mendaftarkan shutdown yang kait. Jika Anda dapat menggunakan ( SIGTERM ) kill -15hook shutdown akan bekerja. ( SIGINT ) kill -2 TIDAK menyebabkan program keluar dengan anggun dan menjalankan hook shutdown.

Mendaftarkan hook shutdown mesin virtual baru.

Mesin virtual Java dimatikan sebagai respons terhadap dua jenis peristiwa:

  • Program keluar secara normal, ketika utas non-daemon terakhir keluar atau ketika metode keluar (setara, System.exit) dipanggil, atau
  • Mesin virtual dihentikan sebagai respons terhadap interupsi pengguna, seperti mengetik ^ C, atau peristiwa di seluruh sistem, seperti logoff pengguna atau pematian sistem.

Saya mencoba program uji berikut di OSX 10.6.3 dan di kill -9atasnya TIDAK menjalankan pengait shutdown, seperti yang diharapkan. Di kill -15atasnya TIDAK menjalankan hook shutdown setiap saat.

public class TestShutdownHook
{
    public static void main(String[] args) throws InterruptedException
    {
        Runtime.getRuntime().addShutdownHook(new Thread()
        {
            @Override
            public void run()
            {
                System.out.println("Shutdown hook ran!");
            }
        });

        while (true)
        {
            Thread.sleep(1000);
        }
    }
}

Tidak ada cara untuk menangani dengan baik kill -9dalam program apapun.

Dalam keadaan yang jarang terjadi, mesin virtual dapat dibatalkan, yaitu berhenti berjalan tanpa mematikan dengan bersih. Ini terjadi ketika mesin virtual diakhiri secara eksternal, misalnya dengan sinyal SIGKILL pada Unix atau panggilan TerminateProcess pada Microsoft Windows.

Satu-satunya pilihan nyata untuk menangani a kill -9adalah dengan meminta program pengamat lain untuk menonton program utama Anda untuk pergi atau menggunakan skrip pembungkus. Anda dapat melakukan ini dengan skrip shell yang melakukan polling psperintah mencari program Anda dalam daftar dan bertindak sesuai ketika program tersebut menghilang.

#!/usr/bin/env bash

java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"
Raedwald
sumber
12

Ada yang cara untuk menangani sinyal Anda sendiri di JVMs tertentu - lihat artikel ini tentang HotSpot JVM misalnya.

Dengan menggunakan sun.misc.Signal.handle(Signal, SignalHandler)panggilan metode internal Sun Anda juga dapat mendaftarkan penangan sinyal, tetapi mungkin tidak untuk sinyal seperti INTatau TERMseperti yang digunakan oleh JVM.

Untuk dapat menangani sinyal apa pun, Anda harus keluar dari JVM dan masuk ke wilayah Sistem Operasi.

Apa yang biasanya saya lakukan untuk (misalnya) mendeteksi penghentian abnormal adalah meluncurkan JVM saya di dalam skrip Perl, tetapi meminta skrip menunggu JVM menggunakan waitpidpanggilan sistem.

Saya kemudian diberi tahu setiap kali JVM ditutup, dan mengapa JVM ditutup, dan dapat mengambil tindakan yang diperlukan.

Asgeir S. Nilsen
sumber
3
Catatan Anda dapat menangkap INTdan TERMdengan sun.misc.Signal, tetapi Anda tidak dapat menangani QUITkarena JVM mencadangkannya untuk debugging, atau KILLkarena OS akan segera menghentikan JVM. Mencoba menangani salah satunya akan memunculkan IllegalArgumentException.
dimo414
12

Saya berharap bahwa JVM dengan anggun menyela ( thread.interrupt()) semua utas yang berjalan yang dibuat oleh aplikasi, setidaknya untuk sinyal SIGINT (kill -2)dan SIGTERM (kill -15).

Dengan cara ini, sinyal akan diteruskan kepada mereka, memungkinkan pembatalan thread dengan anggun dan penyelesaian sumber daya dengan cara standar .

Tapi ini tidak terjadi (setidaknya dalam implementasi JVM saya: Java(TM) SE Runtime Environment (build 1.8.0_25-b17), Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode).

Seperti yang dikomentari pengguna lain, penggunaan hook shutdown tampaknya wajib.

Jadi, bagaimana cara mengatasinya?

Pertama-tama, saya tidak peduli tentang itu di semua program, hanya di program di mana saya ingin melacak pembatalan pengguna dan tujuan yang tidak terduga. Misalnya, bayangkan program java Anda adalah proses yang dikelola oleh orang lain. Anda mungkin ingin membedakan apakah telah dihentikan dengan baik ( SIGTERMdari proses manajer) atau telah terjadi pemadaman (untuk meluncurkan kembali pekerjaan secara otomatis saat permulaan).

Sebagai dasar, saya selalu membuat utas saya yang sudah berjalan lama secara berkala menyadari status terputus dan melempar InterruptedExceptionjika mereka terputus. Ini memungkinkan penyelesaian eksekusi dengan cara yang dikontrol oleh pengembang (juga menghasilkan hasil yang sama seperti operasi pemblokiran standar). Kemudian, di tingkat atas tumpukan benang, InterruptedExceptionditangkap dan pembersihan yang sesuai dilakukan. Utas ini diberi kode untuk mengetahui cara menanggapi permintaan interupsi. Desain kohesi tinggi .

Jadi, dalam kasus ini, saya menambahkan hook shutdown, yang menurut saya harus dilakukan JVM secara default: interupsi semua utas non-daemon yang dibuat oleh aplikasi saya yang masih berjalan:

Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
        System.out.println("Interrupting threads");
        Set<Thread> runningThreads = Thread.getAllStackTraces().keySet();
        for (Thread th : runningThreads) {
            if (th != Thread.currentThread() 
                && !th.isDaemon() 
                && th.getClass().getName().startsWith("org.brutusin")) {
                System.out.println("Interrupting '" + th.getClass() + "' termination");
                th.interrupt();
            }
        }
        for (Thread th : runningThreads) {
            try {
                if (th != Thread.currentThread() 
                && !th.isDaemon() 
                && th.isInterrupted()) {
                    System.out.println("Waiting '" + th.getName() + "' termination");
                    th.join();
                }
            } catch (InterruptedException ex) {
                System.out.println("Shutdown interrupted");
            }
        }
        System.out.println("Shutdown finished");
    }
});

Lengkapi aplikasi pengujian di github: https://github.com/idelvall/kill-test

idelvall.dll
sumber
6

Anda dapat menggunakan Runtime.getRuntime().addShutdownHook(...), tetapi Anda tidak dapat dijamin bahwa itu akan dipanggil dalam hal apa pun .

lexicore.dll
sumber
12
Tetapi dalam kasus kill -9 hampir pasti tidak akan berjalan.
Presiden James K. Polk
1

Ada satu cara untuk bereaksi terhadap pembunuhan -9: yaitu memiliki proses terpisah yang memantau proses yang dimatikan dan membersihkannya setelahnya jika perlu. Ini mungkin akan melibatkan IPC dan akan cukup merepotkan, dan Anda masih dapat menggantinya dengan mematikan kedua proses pada saat yang bersamaan. Saya berasumsi itu tidak akan sepadan dengan masalahnya dalam banyak kasus.

Siapa pun yang membunuh proses dengan -9 secara teoritis harus tahu apa yang dia lakukan dan hal itu mungkin membuat keadaan tidak konsisten.

Arno Schäfer
sumber