R中httr::POST配合withTimeout调用plumber API出现无法抑制报错如何解决
使用R.utils::withTimeout设置超时限制时,调用httr::POST请求plumber API会抛出无法通过常规手段抑制的错误,错误信息如下:
Error in .Call(R_curl_fetch_memory, enc2utf8(url), handle, nonblocking) : reached elapsed time limit
以下方案均无法屏蔽该错误:
- 使用
suppressMessages/suppressWarnings抑制消息与警告输出 - 配置空错误处理逻辑的
tryCatch捕获异常 - 使用
sink函数重定向控制台输出
错误触发后代码可继续运行,但在检测API可用端口时,错误消息会持续推送给用户造成干扰。
以下代码可稳定复现问题(示例使用rstudio jobs包启动独立进程运行plumber服务,移除jobs包后问题仍存在,与该依赖无关)。
plumber.R服务文件内容:
#* quick ping #* @post /ping function() { list(msg = "you got here!") } #* slow 10s call #* @post /slowfxn function() { Sys.sleep(10) 1 }
触发错误的调用代码:
# 启动plumber服务 r <- plumber::plumb("errortest/plumber.R") job::job({r$run(swagger = F,port = 1111)}) # 3s超时的调用函数 call_with_timeout = function() { R.utils::withTimeout( rawToChar(httr::POST("http://127.0.0.1:1111/echo")$content) ,timeout = 3,onTimeout = "silent") } # 启动耗时10s的慢接口调用 job::job({httr::POST(url = "http://127.0.0.1:1111/slowfxn")}) Sys.sleep(1) # 尝试tryCatch捕获 tryCatch( { call_with_timeout() }, error = function(x) {}, TimeoutException = function(x){}, warning = function(x) {} ) # 尝试抑制消息警告 suppressMessages(suppressWarnings(call_with_timeout())) # 尝试sink重定向输出 sink("delete.txt") call_with_timeout() sink(NULL)
根因说明
常规拦截方法失效的核心原因:R.utils::withTimeout依赖R层面的条件系统抛出异常,但httr底层依赖的libcurl为C实现,阻塞请求过程中R层面的中断信号无法立刻终止C代码执行,超时触发时C代码会直接往系统stderr流写入错误文本,这部分输出不经过R的连接系统,因此sink、suppressMessages、tryCatch都无法拦截——上述工具仅能处理R层面抛出的条件、走R输出连接的内容。
优先方案:使用curl原生超时
不要用R.utils::withTimeout给httr请求加超时,httr本身基于libcurl提供了原生超时配置,超时触发的是标准R错误,完全可以被正常捕获,不会出现多余输出:
call_with_timeout = function() { tryCatch({ res <- httr::POST( url = "http://127.0.0.1:1111/ping", config = httr::timeout(3) # 配置原生3s超时 ) rawToChar(res$content) }, error = function(e) { # 超时、连接失败等场景直接返回空值即可 return(NULL) }) }
该方案无额外进程开销,是HTTP请求超时控制的标准实现,优先选用。
兜底方案:子进程隔离调用
如果有特殊场景必须使用R.utils::withTimeout做超时控制,可以把请求逻辑放到独立R子进程中运行,子进程的所有输出(包括C层面直接写stderr的内容)都可以被父进程完全拦截,不会泄露到用户控制台:
library(callr) call_with_timeout = function() { tryCatch({ # 启动独立子进程执行请求,给子进程加4s硬超时兜底 r_bg_result <- r( func = function() { library(httr) R.utils::withTimeout( expr = { res <- httr::POST("http://127.0.0.1:1111/ping") rawToChar(res$content) }, timeout = 3, onTimeout = "silent" ) }, timeout = 4 ) return(r_bg_result) }, error = function(e) { return(NULL) }) }
内容的提问来源于stack exchange,提问作者nolanp2

