CTRL+C w/ Spring Boot & Gradle 杀死 Gradle 守护进程

Posted

技术标签:

【中文标题】CTRL+C w/ Spring Boot & Gradle 杀死 Gradle 守护进程【英文标题】:CTRL+C w/ Spring Boot & Gradle Kills Gradle Daemon 【发布时间】:2016-12-31 14:17:09 【问题描述】:

我使用 Spring Boot Gradle 插件来启动 Tomcat 服务器和我的应用程序。我通过gradle bootRun 启动Tomcat 服务器。我还启用了 Gradle 守护程序,希望让 Gradle 构建速度更快。

但是,启用守护程序是徒劳的。每次我通过 Ctrl + C 停止服务器,然后用gradle bootRun 再次启动服务器时,都会遇到以下消息:

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

Ctrl + C 不仅在 Spring Boot 的掩护下停止了 Tomcat 服务器,还杀死了 Gradle 守护进程。这违背了 Gradle 守护程序模式的目的。

有没有更好的方法来停止服务器,希望通过命令行界面在我使用gradle bootRun 启动 tomcat 的同一终端中保持 Gradle 守护进程的活动?

【问题讨论】:

【参考方案1】:

这仍然是 Gradle 4 中的一个问题。我最好的妥协/解决方案(建立 charlie_pl 的答案):

    ctrl+z将正在运行的进程发送到后台。 杀死进程如:kill $(ps aux | grep "MyApp" | grep -v grep | awk 'print $2') 重启:./gradlew run ...

【讨论】:

其中"MyApp" 可能是"tomcat",并且步骤#3 应该只是运行fg 以恢复Gradle 进程——或者更好的是,将其附加到步骤#2 的末尾作为&& fg 好吧,就我而言,我使用的是 Dropwizard,所以不是tomcat,但可以肯定。也没有通过fg 带回的过程;我们已经在第 2 步中杀死了它。我将进程发送到后台的唯一原因是我不必去新的终端来终止进程。 我的评论特别是关于在 Gradle 中使用 Spring Boottomcat (如 OP 所述)。如果你不使用 Spring Boot,情况可能会有所不同,但我的直觉是,如果你不打算运行 fg,你还不如只运行 Ctrl-C。顺便说一句,当你运行fg 时,它允许 Spring/Gradle 在返回命令提示符之前进行一些清理。【参考方案2】:

我不熟悉 Spring Boot 插件,所以大概没有“bootStop”命令(就像在 Jetty 插件中一样)。此外,经过广泛搜索后,我认为没有所需结果的命令行选项。

一个选项,虽然公认是一个杂项,是使用工具 API。 (完整代码示例is here。)

这个想法是在 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)
    

可以轻松自定义此代码以执行其他任务、重新启动任务等。它暗示了一个围绕个人命令行使用的美化包装。

【讨论】:

【参考方案3】:

这里是核心开发人员解释为什么 Ctrl + C 会杀死守护进程。

“设计”一直都是这种方式,但我们想远离它,这样守护程序就不会经常被杀死。我认为在某些情况下我们不传播 ctrl+c,但那是幸运的。

如果您看看我们在 2.5 中为连续模式所做的事情,我们正在添加 ctrl+d 以退出 Gradle 进程而不杀死守护进程。我们的 Play 应用程序支持 (playRun) 存在与 bootRun 类似的问题,它使用相同的机制 (ctrl+d)。我认为我们最终会做这样的事情,但我们需要为现有的构建脚本提供一种替代方式,以便在我们一直捕获输入之前读取标准输入。

——Sterling Greene(Gradle 核心开发)

【讨论】:

【参考方案4】:

看起来这可能已在 3.1 https://docs.gradle.org/current/release-notes#more-resilient-daemon 中得到解决

【讨论】:

现在正确的链接是docs.gradle.org/3.1/release-notes.html#more-resilient-daemon @StephenFriedrich 您可以尝试简单地杀死应用程序进程,这应该保留守护进程,请参阅我的答案。【参考方案5】:

bootRunspring-boot-gradle-plugin 的一个便利功能。它允许您在一个命令中执行两个步骤,并且它具有非常小的好处,即在此过程中不生成.jar 文件。它还具有潜在的主要好处......

如果 devtools 已添加到您的项目中,它将自动 监控您的应用程序的更改。

如果您没有使用 bootRun 的实时更新功能,您可以通过将构建/运行序列作为两个命令执行来解决此问题,如 "Running your application" 中所述。由于第二个命令不涉及 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 一样无缝

【讨论】:

【参考方案6】:

我遇到了同样的问题。我启动了 dropwizard 应用程序,并且杀死一个守护进程显着增加了重新启动应用程序的时间。

简单的解决方案: 最后,我只是简单地搜索了 dropwizard 进程,并在命令行中将其杀死(简单的 kill & ps aux & grep 组合)。这会关闭应用程序并导致构建失败,但会保留守护进程。

【讨论】:

以上是关于CTRL+C w/ Spring Boot & Gradle 杀死 Gradle 守护进程的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot + docker +mongo

Spring Boot 2 Actuator Starter 中的 Nullpointer w。 Tomcat指标

spring boot liunx 怎么启动

Spring Boot w/o starter-parent pom 不加载 jdbc 驱动程序

如何在 Spring Boot 2(w/WebFlux)中为 HTTP 和 HTTPS 配置两个端口?

Spring Boot web app w/session + CSRF & stateless Basic Auth w/o CSRF