Отключающий крюк не заканчивает основной поток - PullRequest
0 голосов
/ 04 июля 2018

Я пытаюсь завершить работу моего сервера с помощью сочетания клавиш ctrl-c, и я искал какой-то способ сделать это и нашел эту запись.

Я попробовал это решение, но когда завершается ловушка завершения работы, мой основной поток также завершается без выполнения задач завершения работы сервера, и процесс завершается с кодом 1.

Вот мой код:

package test;

import java.io.IOException;
import java.net.ServerSocket;

public class Main implements Runnable {
    private ServerSocket serverSocket;

    private Main() {
        try {
            serverSocket = new ServerSocket(5000);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(10);
        }

        System.err.println("Waiting ctrl-c ...");

        Runtime.getRuntime().addShutdownHook(new Thread(this));

        try {
            while (serverSocket.accept() != null) {
                doSomething();
            }
            System.err.println("end while");
        } catch (IOException e) {
            System.err.println("end exception");
        } finally {
            System.err.println("trying to close the server...");
            try {
                // simulate a long job
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // this line was never reached
            System.err.println("server closed");
        }

    }

    public static void main(String[] args) {
        new Main();
    }

    private void doSomething() {}

    @Override
    public void run() {
        System.err.println("ctrl-c pressed");
        try {
            System.err.println("trying to close socket");
            serverSocket.close();
            System.err.println("socket closed!!!");
        } catch (IOException e) {
            System.err.println("failed to close socket");
        }
    }
}

Вот вывод из среды IntelliJ IDE:

Waiting ctrl-c ...
ctrl-c pressed
trying to close socket
socket closed!!!
end exception
trying to close the server...

Process finished with exit code 1

Как мне элегантно решить эту проблему?

Ответы [ 2 ]

0 голосов
/ 31 марта 2019

Я думаю, что здесь происходит то, что семантика ловушки завершения работы не очень хорошо документирована (не удалось найти это нигде в официальных документах или в постах блога).

Я думаю, что это происходит из-за того, что JVM завершает работу сразу после того, как все потоки перехватчиков завершения вернулись (в случае получения сигнала) , не дожидаясь завершения основного потока .

Вы можете смоделировать это с помощью следующей небольшой программы:

package cosenmarco;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {

    private static volatile CompletableFuture<Void> shutdownFuture = new CompletableFuture<>();

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            shutdownFuture.complete(null);
            System.err.println("Shutdown signal");
        }));

        try {
            shutdownFuture.get();
            Thread.sleep(1000); // Comment this to have "Finished" correctly printed
            System.err.println("Finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Также никакого прерывания потока не происходит: я думаю, что поток на самом деле просто завершен.

Что вы, возможно, захотите сделать (я где-то видел подобный код, но не могу вспомнить его прямо сейчас), это получить ссылку на основной поток и присоединиться к нему в хуке shutdown. Как-то так у меня работает:

package cosenmarco;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {

    private static volatile CompletableFuture<Void> shutdownFuture = new CompletableFuture<>();

    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            shutdownFuture.complete(null);
            System.err.println("Shutdown signal");
            try {
                mainThread.join();
                System.err.println("Joined on main thread.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));

        try {
            shutdownFuture.get();
            Thread.sleep(1000);
            System.err.println("Finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Это печатает (после нажатия Ctrl + C) следующее:

^CShutdown signal
Finished
Joined on main thread.
0 голосов
/ 04 июля 2018

когда завершается ловушка завершения работы, мой основной поток также завершает работу без выполнения задач завершения работы сервера

Вероятно, до завершения отключения. Это правильное поведение. Основной поток принудительно завершается JVM. Это эффект CTRL / C.

Это также означает, что ваш хук отключения в принципе бессмысленен. Вы можете удалить его полностью, и основной поток будет по-прежнему выходить таким же образом.

«Задачи выключения сервера» должны быть в ловушке выключения. Вот для чего это. Но они не должны быть блокирующими или продолжительными: см. Javadoc.

NB ServerSocket.accept() не возвращает ноль. Не пишите бессмысленные тесты.

...