Shiny应用关闭时如何终止所有关联的正在运行进程?
问题根因
原代码使用阻塞式的processx::run(wait=TRUE)调用,会完全卡住Shiny服务端的事件循环,导致服务端无法感知到客户端已经断开连接,自然也无法执行子进程清理逻辑,最终出现残留进程阻塞后续请求的问题。
解决方案
Linux环境下可以通过非阻塞子进程+会话结束回调的方案彻底解决该问题:
核心实现思路
- 放弃阻塞式的
processx::run()调用,改用processx::process类创建非阻塞子进程,避免阻塞Shiny会话的事件循环,让会话可以正常监听到客户端断开事件 - 注册
session$onSessionEnded回调函数,在用户关闭浏览器、会话终止时主动杀死关联的子进程及其衍生的所有子进程,避免孤儿进程残留 - 通过
invalidateLater循环监听进程状态,实现原来同步等待运行结束的业务逻辑,同时不阻塞会话事件响应
可运行修改版代码
library(shiny) library(processx) ui <- fluidPage( actionButton("runBtn", label="Run a program that consumes many resources"), verbatimTextOutput("status") ) server <- function(input, output, session) { current_process <- NULL process_status <- reactiveVal("Idle") observeEvent(input$runBtn, { if (!is.null(current_process) && current_process$is_alive()) { process_status("Already running, please wait or refresh the page to terminate") return() } current_process <<- process$new( command = "sleep", args = "240", cleanup_tree = TRUE, supervise = TRUE ) process_status("Process running...") session$onSessionEnded(function() { if (!is.null(current_process) && current_process$is_alive()) { current_process$kill_tree() # Linux专属兜底杀进程逻辑,确保多级子进程全被清理 # system(paste0("kill -TERM -- -", current_process$get_pid()), wait = FALSE) } }) observe({ invalidateLater(1000, session) if (is.null(current_process) || !current_process$is_alive()) { process_status("Process finished") current_process <<- NULL } }) }) output$status <- renderPrint(process_status()) } shinyApp(ui, server)
关键说明
cleanup_tree = TRUE:processx 3.2.0+版本支持的参数,主进程被终止时会自动杀死其衍生的所有子进程,避免孤儿进程残留session$onSessionEnded:Shiny原生会话生命周期回调,会在用户关闭浏览器、页面刷新、会话超时的时候触发,是执行清理逻辑的最佳时机- 如果你运行的重型程序会启动多级子进程,可以打开Linux专属兜底杀进程逻辑的注释,直接杀死整个进程组下的所有进程,不会有任何残留
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

