CTRL+C с Spring Boot и Gradle убивает Gradle Daemon

Я использую плагин Spring Boot Gradle для запуска сервера Tomcat и моего приложения. Я запускаю сервер Tomcat через gradle bootRun, Я также включил демон Gradle в надежде ускорить сборку Gradle.

Тем не менее, включение демона ничего не значит. Каждый раз, когда я останавливаю сервер с помощью Ctrl + C, затем снова запускаю сервер с gradle bootRunЯ сталкиваюсь с сообщением:

Starting a new Gradle Daemon for this build (subsequent builds will be faster).

Ctrl + C не только останавливает сервер Tomcat под прикрытием Spring Boot, но также убивает демона Gradle. Который побеждает цель режима демона Gradle.

Есть ли лучший способ остановить сервер, надеюсь, через интерфейс командной строки в том же терминале, для которого я запустил tomcat? gradle bootRun, что поддерживает демона Gradle?

6 ответов

Это все еще проблема в Gradle 4. Мой лучший компромисс / решение (построение ответа charlie_pl):

  1. Нажмите ctrl+z отправить запущенный процесс в фоновый режим.
  2. Убить процесс как: kill $(ps aux | grep "MyApp" | grep -v grep | awk '{print $2}')
  3. Запустить снова: ./gradlew run ...

Я не знаком с плагином Spring Boot, поэтому предположительно нет команды "bootStop" (как в плагине Jetty). Кроме того, после тщательного поиска, я не думаю, что есть опция командной строки для желаемого результата.

Один из вариантов, хотя и по общему признанию, это использование Tooling API. (Полный пример кода здесь.)

Идея состоит в том, чтобы запустить долгосрочную задачу в скрипте Groovy. По команде скрипт остановит задачу и вызовет gradle tasks пощекотать демона.

Из приведенного выше кода GitHub долгосрочное задание может быть следующим:

task runService() << {
    ant.delete(file: "runService.log")
    def count = 0
    while(true) {
        new File("runService.log").withWriterAppend {
            it.writeLine("[runService] count: ${count}")
        }
        println "sleeping ...."
        try { Thread.sleep(5 * 1000) } catch (Exception ex) {}
        count++
    }
}

Идея скрипта Groovy состоит в том, чтобы запустить задачу в фоновом потоке, а затем отправить токен отмены при получении команды.

Для ясности проиллюстрирую два раздела. Первый раздел - это фоновый поток:

class BuildRunner implements Runnable {
    def connector
    def tokenSource
    def taskName

    BuildRunner(connector, tokenSource, taskName) {
        this.connector = connector
        this.tokenSource = tokenSource
        this.taskName = taskName
    }

    public void run() {
        def connection = connector.connect()        
        try {            
            def build = connection.newBuild()
            build.withCancellationToken(tokenSource.token())
            build.setStandardOutput(System.out)
            build.setStandardError(System.err)
            build.forTasks(taskName)
            build.run()
            println "${taskName} is finishing ..."
        } catch(BuildCancelledException bcex) {
            println "received cancel signal"
            println "tickling daemon ..."
            tickleDaemon(connector)
            println "Done."
            System.exit(0)
        } catch(Exception ex) {
            println "caught exception : " + ex
        } finally {            
          connection.close()        
        }        
    }

    def tickleDaemon = { connector ->
        final String TASKS = "tasks"
        def connection = connector.connect()        
        def build = connection.newBuild()
        build.forTasks(TASKS)
        build.run()
    }
}

а другой раздел - главная консоль:

// main -----------

// edit as appropriate
final String TASK_NAME = "runService"
final String GRADLE_INSTALL_DIR = "/Users/measter/tools/gradle-2.14.1"
final String PROJECT_DIR = "../service"

def connector = GradleConnector.newConnector()
connector.useInstallation(new File(GRADLE_INSTALL_DIR))
connector.forProjectDirectory(new File(PROJECT_DIR))

def tokenSource = connector.newCancellationTokenSource()

println "starting ${TASK_NAME}"
def buildRunner = new BuildRunner(connector, tokenSource, TASK_NAME)
new Thread(buildRunner).start()

def console = new Scanner(System.in)
println "Enter a command (S: stop task, Q: quit): "

while (console.hasNextLine()) {
    def lineTokenizer = new Scanner(console.nextLine())
    String token = lineTokenizer.next()
    if (token.equalsIgnoreCase("S")) {
        tokenSource.cancel()
    } else if (token.equalsIgnoreCase("Q")) {
        println "Done."
        System.exit(0)
    }
}

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

Вот объяснение от основного разработчика, почему Ctrl + C убьет демона.

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

Если вы посмотрите на то, что мы делаем для непрерывного режима в 2.5, мы добавим Ctrl+ D, чтобы выйти из процесса Gradle, не убивая демона. У нас есть проблема, похожая на bootRun, с нашей поддержкой приложений Play (playRun), которая использует тот же механизм (ctrl+d). Я думаю, что в конечном итоге мы сделаем что-то подобное в общем, но нам нужно предоставить альтернативный способ для существующих сценариев сборки читать stdin, прежде чем мы будем постоянно фиксировать ввод.

- Стерлинг Грин (разработчик Gradle Core Dev)

bootRun это удобная особенность spring-boot-gradle-plugin, Это позволяет вам выполнить два шага в одной команде, и имеет очень незначительное преимущество - не генерировать .jar файл в процессе. Это также имеет потенциально большую выгоду...

Если devtools был добавлен в ваш проект, он будет автоматически следить за изменениями в вашем приложении.

Если вы не используете функции живого обновления bootRun Вы можете решить эту проблему, выполнив последовательность сборки / запуска в виде двух команд, как описано в разделе "Запуск приложения". Поскольку вторая команда не включает Gradle, теперь вы можете Ctrl-C сервер без недостатка, оставив Gradle в CANCELED государство.

Вот пример применения такого подхода:

gradle build && java -jar build/libs/myproject-0.0.1-SNAPSHOT.jar

С другой стороны, если вы используете devtools, вам может не потребоваться часто перезагружать сервер вручную - просто пересоберите и сервер перезапустится сам (для Groovy вам нужно только обновить исходный файл).

Приложения, использующие spring-boot-devtools, будут автоматически перезапускаться всякий раз, когда изменяются файлы в пути к классам.

./gradlew build -x test

... И если вы используете IDE (например, vscode), он может автоматически скомпилировать ваши java-файлы, поэтому простое сохранение java-файла может инициировать перезапуск сервера, косвенно. Тогда Java станет таким же цельным, как Groovy в этом отношении.

Похоже, это могло быть решено в 3.1 https://docs.gradle.org/current/release-notes

У меня такая же проблема. Я запустил приложение dropwizard, и убийство демона значительно увеличило время перезапуска приложения.

Простое решение: в конце концов я просто искал процесс dropwizard и уничтожил его в командной строке (простая комбинация kill & ps aux & grep). Это закрывает приложение и дает сбой при сборке, но сохраняет демон.

Другие вопросы по тегам