Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve shiny prerendered process by avoiding race condition when writing file #2502

Open
cderv opened this issue Jul 26, 2023 · 1 comment
Labels
feature a feature request or enhancement next to consider for next release theme: paths path related improvment / issue

Comments

@cderv
Copy link
Collaborator

cderv commented Jul 26, 2023

I have a suspicion that there are somehow two processes running at the same time to prerender the source document and are inadvertently writing into the same file. I've uncovered a reproducible example that produces the expected error (here shown before the fix in #2500).

render_bg <- function(path) {
  callr::r_bg(
    function(path) {
      rmarkdown::render(path)
    },
    args = list(path = path)
  )
}

# Remove prerendered HTML and source if they exist
unlink("issue.html")
unlink("issue.Rmd")

writeLines('---
title: "issue"
output: html_document
runtime: shiny_prerendered
---

```{r}
textInput("text", "Text", "Text")
renderText(input$text)
```', "issue.Rmd"
)

# Render the Rmd twice at basically the same time
r1 <- render_bg("issue.Rmd")
r2 <- render_bg("issue.Rmd")
r2$wait()

When you try to run the document, it will have been corrupted by simultaneous writes into the .html file.

rmarkdown::run("issue.Rmd")
#> Error: parse error: trailing garbage
#>           ":{},"value":["2.23.3"]}]}]} {"type":"list","attributes":{},
#>                      (right here) ------^

After applying the fix in #2500, we still get errors due to failures in other places in the file.

rmarkdown::run("issue.Rmd")
#> Error: lexical error: invalid char in json text.
#>           s":{},"value":["jquerylib"]} <!--html_preserve--> {"type":"l
#>                      (right here) ------^

Originally posted by @gadenbuie in #2499 (comment)

In this case, my reprex is a hypothetical example that's at least consistent with the real-world scenario. I've heard reports of this kind of problem for learnr users on Connect and shinyapps.io as well as Posit Cloud. Cloud is surprising to me because in theory it should be equivalent to a local installation, where this problem hasn't been reported really.

Related to the concurrency option, I understand that there are many reasons that make writing unique files difficult. That said, if we're using a mechanism like writeLines(text, file_connection), I do think rmarkdown/knitr should write into a temporary uniquely-named file that's moved into place when the contents are finished. This way, even if there are race conditions, then at least the final intermediate file is the atomically the complete and correct winner of the race.

Originally posted by @gadenbuie in #2499 (comment)

@cderv cderv added theme: paths path related improvment / issue feature a feature request or enhancement labels Jul 26, 2023
@cderv cderv added the next to consider for next release label Jul 26, 2023
@grosscol
Copy link

In the interim quick solution to this it to render in different intermediates directory for each document. The longer term solution is to generate a prefix for the intermediate template.knit.md

multi.sh

#!/usr/bin/env bash

Rscript multi.r id_1 &\
  Rscript multi.r id_2 &\
  Rscript multi.r id_3

echo "done"

multi.r

library(rmarkdown)

args <- commandArgs(trailingOnly = T)

# Fill in rmarkdown environment
rmd_env <- new.env()
rmd_env$foo <- "bar"
rmd_env$identifier <- args[1]

# rendering
outfile <- paste0(args[1], ".pdf")

render(input = 'multi.rmd',
       output_file = outfile,
       output_format = 'pdf_document',
       envir = rmd_env,
       runtime = 'static',
       clean = F,
       intermediates_dir = args[1],
       output_dir = args[1])

# Move rendered output file
rendered_path <- file.path(args[1], outfile)
collected_path <- file.path('.', outfile)

file.rename(from = rendered_path,
            to = collected_path)

multi.rmd

---
title: "mutli"
author: "GcL"
date: "2024-05-28"
output: pdf_document
---

``​`{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
``​`

## Env Demo

Passed environment should include the variable foo and identifier.

``​`{r static}
print(paste("foo:", foo))
``​`

``​`{r args}
print(paste("identifier:", identifier))
``​`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature request or enhancement next to consider for next release theme: paths path related improvment / issue
Projects
Status: Backlog
Development

No branches or pull requests

2 participants