带有 invalidateLater() 的简单 Shiny 应用程序错误地删除了持久的后台进程

问题描述

我正在尝试编写一个最小的 Shiny 应用程序,它维护一个持久的外部后台进程。出于全尺寸用例的特定原因,我在文本文件中跟踪 PID,而不是仅使用 processx 句柄。当我启动应用程序时,它看起来像这样:

enter image description here

当我按下“开始”按钮时,应用程序会创建一个后台进程并将 PID 记录在一个文本文件中。带有 invalidateLater() 的响应式上下文会重复读取文本文件显示 PID 和状态。

enter image description here

该进程应该一直运行,直到我单击“停止”。但在初始化后不到一秒钟,该进程自行退出

enter image description here

如果我删除 invalidateLater(),进程会继续运行。或者,如果我使用 processx 句柄而不是 ps 和文本文件,应用程序也能工作,但这对于我的用例来说还不够。

应用代码

library(callr)
library(ps)
library(shiny)
library(tools)

ui <- fluidPage(
  actionButton("start","start"),actionButton("stop","stop"),textoutput("status")
)

server <- function(input,output,session) {
  file <- tempfile()
  observeEvent(input$start,{
    px <- r_bg(function() Sys.sleep(Inf))
    writeLines(as.character(px$get_pid()),file)
  })
  observeEvent(input$stop,{
    pid <- as.integer(readLines(file))
    if (pid %in% ps_pids()) {
      ps_kill(ps_handle(pid))
    }
  })
  output$status <- renderText({
    invalidateLater(100)
    if (file.exists(file)) {
      pid <- as.integer(readLines(file))
      paste(ifelse(pid %in% ps_pids(),"running","stopped"),pid)
    }
  })
}

shinyApp(ui = ui,server = server)

会话信息

R version 4.0.3 (2020-10-10)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Catalina 10.15.7

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] tools     stats     graphics  Grdevices utils     datasets  methods  
[8] base     

other attached packages:
[1] shiny_1.6.0 ps_1.5.0    callr_3.5.1

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.6        magrittr_2.0.1    xtable_1.8-4      R6_2.5.0         
 [5] rlang_0.4.10      fastmap_1.1.0     jquerylib_0.1.3   htmltools_0.5.1.1
 [9] ellipsis_0.3.1    yaml_2.2.1        digest_0.6.27     lifecycle_1.0.0  
[13] processx_3.4.5    later_1.1.0.1     sass_0.3.1        promises_1.2.0.1 
[17] rsconnect_0.8.16  cachem_1.0.4      mime_0.10         compiler_4.0.3   
[21] bslib_0.2.4       jsonlite_1.7.2    httpuv_1.5.5   

编辑:垃圾收集

这是因为垃圾收集的 processx 句柄。我可以用 2 个 R 会话来证明这一点。会话 1 创建一个后台进程。

px <- r_bg(function() Sys.sleep(Inf))
px$get_pid()
#> [1] 8252

会话 2 循环检查从会话 1 产生的后台进程。

library(ps)
while(TRUE) {
  print(8252 %in% ps_pids())
  Sys.sleep(1)
}

会话 2 开始时每秒打印 TRUE。但是当我在会话 1 中调用 rm(px); gc() 时,会话 2 打印 FALSE

我现在看到垃圾回收的终止是 processx: https://github.com/r-lib/processx#features一个故意特性。我想在大多数情况下都是合理的。

解决方法

如果我在 cleanup 中将 TRUE 设置为 callr::r_bg(),该过程会继续进行。问题解决了。