tests/testthat/test-python-threads.R

test_that("py_allow_threads() can enable/disable background threads", {

  file <- tempfile()
  on.exit(unlink(file), add = TRUE)

  write_to_file_from_thread <- py_run_string("
def write_to_file_from_thread(path, lines):
    from time import sleep, localtime, strftime

    def write_to_file(path, lines):
        sleep(.1) # don't try to run until we've had a chance to return to the R main thread
        with open(path, 'w') as f:
            for line in list(lines):
                f.write(line + '\\n')

    from _thread import start_new_thread
    start_new_thread(write_to_file, (path, lines))
", local = TRUE)$write_to_file_from_thread

  reticulate:::py_allow_threads_impl(FALSE)
  write_to_file_from_thread(file, letters)
  Sys.sleep(.5)
  # confirm background thread did not run while R was sleeping
  expect_false(file.exists(file))
  # explicitly enter python and release the gil
  import("time")$sleep(.3)
  # confirm the background thread ran on py_sleep()
  expect_identical(readLines(file), letters)

  unlink(file)

  reticulate:::py_allow_threads_impl(TRUE)
  write_to_file_from_thread(file, letters)
  Sys.sleep(.3)
  # confirm that the background thread ran while R was sleeping.
  expect_identical(readLines(file), letters)

})



test_that("Python calls into R from a background thread are evaluated", {

  x <- 0L
  r_func <- function() x <<- x+1
  py_file <- withr::local_tempfile(lines = "r_func()", fileext = ".py")

  reticulate:::py_run_file_on_thread(
    py_file,
    init_globals = list(r_func = r_func)
  )

  # Simulate the main R thread doing non-Python work (e.g., sleeping)
  for(i in 1:10) {
    Sys.sleep(.01 * i)
    if (x != 0L) break
  }

  expect_equal(x, 1L)
})


test_that("Errors from background threads calling into main thread are handled", {

  signal_r_error <- function() stop("foo-bar-baz")

  val <- NULL
  set_val <- function(v) val <<- v

  py_file <- withr::local_tempfile(lines = "
try: signal_r_error()
except Exception as e: set_val(e.args[0])
", fileext = ".py")

  reticulate:::py_run_file_on_thread(py_file, init_globals = list(
    signal_r_error = signal_r_error,
    set_val = set_val
  ))

  # Simulate the main R thread doing non-Python work (e.g., sleeping)
  for(i in 1:10) {
    Sys.sleep(.01 * i)
    if(!is.null(val)) break
  }

  expect(!is.null(val), "`thread_err_msg` never created")
  expect_equal(val, "foo-bar-baz")

})
rstudio/reticulate documentation built on Oct. 29, 2024, 8:07 a.m.