rmarkdown渲染NSE函数仅在callr调用场景下运行失败问题
问题描述
我遇到了NSE、rmarkdown和callr三者联动的异常问题,和同类RMarkdown禁用callr的问题类型相似。
当我定义变量用于通过嵌套eval和列表调用、基于NSE实现的类filter逻辑时,仅在callr环境中运行会触发报错,直接运行则正常。
复现用Rmd文件内容
--- title: "Test CC2 Failure Reproducibly" author: "Robert M Flight" output: rmarkdown::md_document editor_options: chunk_output_type: console --- ```{r run_enrichments} set.seed(1234) create_go_annotation = function(db, ontology = NULL){ all_genes = keys(db) go_all_gene = AnnotationDbi::select(db, keys = all_genes, columns = c("GOALL", "ONTOLOGYALL")) if (!is.null(ontology)) { go_all_gene = go_all_gene[go_all_gene$ONTOLOGYALL == ontology, ] ontology_type = paste0("GO.", ontology) } else { ontology_type = "GO.all" } go_2_gene = split(go_all_gene$ENTREZID, go_all_gene$GOALL) go_2_gene = lapply(go_2_gene, unique) go_desc = AnnotationDbi::select(GO.db::GO.db, keys = names(go_2_gene), columns = "TERM", keytype = "GOID")$TERM names(go_desc) = names(go_2_gene) go_annotation = categoryCompare2::annotation(annotation_features = go_2_gene, description = go_desc, annotation_type = ontology_type, feature_type = "ENTREZID") go_annotation } library(org.Hs.eg.db) library(GO.db) library(categoryCompare2) library(methods) go_mf = create_go_annotation(org.Hs.eg.db, "MF") all_features = unique(unlist(go_mf@annotation_features)) sig_features = sample(all_features, 500) enrich = hypergeometric_feature_enrichment( new("hypergeom_features", significant = sig_features, universe = all_features, annotation = go_mf), p_adjust = "BH" ) comb_enrich = combine_enrichments(sig1 = enrich) sig_cutoff = 0.1 filter_enrich = get_significant_annotations(comb_enrich, padjust <= sig_cutoff)
## callr运行命令与报错信息 ```r r(function() rmarkdown::render(here::here("categoryCompare2_failure.Rmd"), output_file = "cc2_fails_1.md"), show = TRUE) # Quitting from lines 13-57 (categoryCompare2_failure.Rmd) # # Error in FUN(X[[i]], ...) : object 'sig_cutoff' not found # Error: callr subprocess failed: object 'sig_cutoff' not found # > .Last.error.trace # Stack trace: # Process 221313: # 1. callr:::r(function() rmarkdown::render(here::he ... # 2. callr:::get_result(output = out, options) # 3. throw(newerr, parent = remerr[[2]]) # x callr subprocess failed: object 'sig_cutoff' not found # Process 221619: # 15. (function () ... # 16. rmarkdown::render(here::here("categoryCompare2_failure.Rmd")) # 17. knitr::knit(knit_input, knit_output, envir = envir, quiet ... # 18. knitr:::process_file(text, output) # 19. base:::withCallingHandlers(if (tangle) process_tangle(gro ... # 20. knitr:::process_group(group) # 21. knitr:::process_group.block(group) # 22. knitr:::call_block(x) # 23. knitr:::block_exec(params) # 24. knitr:::eng_r(options) # 25. knitr:::in_dir(input_dir(), evaluate(code, envir = env, n ... # 26. knitr:::evaluate(code, envir = env, new_device = FALSE, k ... # 27. evaluate::evaluate(...) # 28. evaluate:::evaluate_call(expr, parsed$src[[i]], envir = e ... # 29. evaluate:::timing_fn(handle(ev <- withCallingHandlers(wit ... # 30. base:::handle(ev <- withCallingHandlers(withVisible(eval( ... # 31. base:::withCallingHandlers(withVisible(eval(expr, envir, ... # 32. base:::withVisible(eval(expr, envir, enclos)) # 33. base:::eval(expr, envir, enclos) # 34. base:::eval(expr, envir, enclos) # 35. categoryCompare2:::get_significant_annotations(comb_enric ... # 36. categoryCompare2:::get_significant_annotations(comb_enric ... # 37. categoryCompare2:::.get_significant_combined_enrichment(i ... # 38. base:::lapply(in_results@enriched, function(x) { ... # 39. categoryCompare2:::FUN(X[[i]], ...) # 40. categoryCompare2:::get_significant_annotations(x@statisti ... # 41. categoryCompare2:::get_significant_annotations(x@statisti ... # 42. categoryCompare2:::.get_significant_stat_results(in_resul ... # 43. categoryCompare2:::multi_query_list(in_results@statistic_ ... # 44. base:::lapply(queries, eval, list_to_query) # 45. base:::FUN(X[[i]], ...) # 46. base:::FUN(X[[i]], ...) # 47. base:::.handleSimpleError(function (e) ... # 48. h(simpleError(msg, call)) # x object 'sig_cutoff' not found
报错提示找不到sig_cutoff对象,但该变量确实存在于运行环境中,并未被正常向下传递。
直接运行rmarkdown渲染则可正常执行:
rmarkdown::render(here::here("categoryCompare2_failure.Rmd"), output_file = "cc2_works.md")
可确认该问题与callr相关,使用targets包时也会触发相同报错。
涉及版本:callr v 1.0.7, rmarkdown v 2.11, R 4.1.0
问题成因
这个问题是非标准评估(NSE)的环境链查找逻辑和callr独立子进程的环境结构不兼容导致的:
categoryCompare2包的get_significant_annotations函数内部实现类filter的NSE逻辑时,调用eval解析用户传入的padjust <= sig_cutoff表达式,仅指定了数据查询环境(存储padjust等字段的列表),没有显式指定父查找环境,默认会用eval被调用位置的包内部环境,而非用户传入表达式的调用环境。- 直接运行
rmarkdown::render时,代码块运行环境属于当前R会话的全局环境子环境,eval的默认查找链能向上找到代码块里定义的sig_cutoff变量,所以运行正常。 callr启动的是完全独立的R子进程,子进程内的环境链结构和主进程不同:callr::r()传入的匿名函数运行环境、rmarkdown渲染的代码块环境、包内部函数运行环境三者的链断开,包内部的eval调用顺着默认环境链查找不到代码块里定义的sig_cutoff,就会报找不到对象的错误。使用targets包时触发同样问题也是这个原因,因为targets默认用callr运行任务。
修复方案
用户端临时修复(无需修改包代码)
任选一种即可:
- 用
!!运算符直接把变量值注入到表达式里,避免运行时环境查找:
filter_enrich = get_significant_annotations(comb_enrich, padjust <= !!sig_cutoff)
- 把变量临时写入子进程的全局环境,保证NSE查找路径通顺:
sig_cutoff = 0.1 assign("sig_cutoff", sig_cutoff, envir = .GlobalEnv) filter_enrich = get_significant_annotations(comb_enrich, padjust <= sig_cutoff)
包层面永久修复
修改categoryCompare2包内部multi_query_list函数的eval调用,显式指定父查找环境为用户调用函数时的环境:
把原来的实现:
lapply(queries, eval, list_to_query)
修改为:
lapply(queries, function(x) eval(x, list_to_query, enclos = parent.frame()))
内容的提问来源于stack exchange,提问作者rmflight
相关产品推荐
相关产品推荐

