diff --git a/R/thread.R b/R/thread.R index 10f2e6f9f..88f5c38b3 100644 --- a/R/thread.R +++ b/R/thread.R @@ -53,7 +53,32 @@ py_allow_threads <- function(allow = TRUE) { py_run_file_on_thread <- function(file, ..., args = NULL) { if (!is.null(args)) args <- as.list(as.character(args)) - import("rpytools.run")$`_launch_lsp_server_on_thread`(file, args) + + # TODO: we should have a dedicated entry point in reticulate for this. + # Needs to be updated in ark and positron. + launching_lsp <- (basename(file) == 'positron_language_server.py' && + is_positron() && + basename(dirname(file)) == "positron") + + if (launching_lsp) { + main_dict <- py_eval("__import__('__main__').__dict__.copy()", FALSE) + py_get_attr(main_dict, "pop")("__annotations__") + } + + import("rpytools.run")$run_file_on_thread(file, args, ...) + + if (launching_lsp) { + + PositronIPKernelApp <- import("positron_ipykernel.positron_ipkernel")$PositronIPKernelApp + for(i in 1:40) { # Positron timeout is 20 seconds + if (PositronIPKernelApp$initialized()) break + Sys.sleep(.5) + } + Sys.sleep(1) + + py_eval("__import__('__main__').__dict__.update", FALSE)(main_dict) + } + invisible() } ## used in Positron: diff --git a/inst/python/rpytools/run.py b/inst/python/rpytools/run.py index a285c4997..46c33682b 100644 --- a/inst/python/rpytools/run.py +++ b/inst/python/rpytools/run.py @@ -38,7 +38,7 @@ def __exit__(self, *_): if self.argv is not None: # restore sys.argv if it's unmodified from what we set it to. # otherwise, leave it as-is. - patched_argv = [self.path] + list(self.args) + patched_argv = [self.path] + list(self.argv) if sys.argv == patched_argv: sys.argv = self._orig_sys_argv @@ -50,12 +50,19 @@ def _launch_lsp_server_on_thread(path, args): return run_file_on_thread(path, args) - -def run_file_on_thread(path, args=None): +def run_file_on_thread(path, argv=None, init_globals=None, run_name="__main__"): # for now, leave sys.argv and sys.path permanently modified. # Later, revisit if it's desirable/safe to restore after the initial # lsp event loop startup. - RunMainScriptContext(path, args).__enter__() import _thread - - _thread.start_new_thread(run_file, (path,)) + from runpy import run_path + + RunMainScriptContext(path, argv).__enter__() + _thread.start_new_thread( + run_path, + (path,), + { + "run_name": run_name, + "init_globals": init_globals, + }, + ) diff --git a/tests/testthat/test-python-threads.R b/tests/testthat/test-python-threads.R index 23682dac1..f5cfe8275 100644 --- a/tests/testthat/test-python-threads.R +++ b/tests/testthat/test-python-threads.R @@ -44,11 +44,13 @@ def write_to_file_from_thread(path, lines): test_that("Python calls into R from a background thread are evaluated", { x <- 0L - py$r_func <- function() x <<- x+1 - on.exit(py_del_attr(py, "r_func"), add = TRUE) + r_func <- function() x <<- x+1 py_file <- withr::local_tempfile(lines = "r_func()", fileext = ".py") - reticulate:::py_run_file_on_thread(py_file) + 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) { @@ -62,21 +64,20 @@ test_that("Python calls into R from a background thread are evaluated", { test_that("Errors from background threads calling into main thread are handled", { - py$signal_r_error <- function() stop("foo-bar-baz") - on.exit(py_del_attr(py, "signal_r_error"), add = TRUE) + signal_r_error <- function() stop("foo-bar-baz") - # when testing, `r` in Python resolves `getOption("rlang_trace_top_env"), not - # the R globalenv(). Avoid using it to communicate. val <- NULL - py$set_val <- function(v) val <<- v - on.exit(py_del_attr(py, "set_val"), add = TRUE) + 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) + 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) {