Skip to content

Warning: Graphics device changes when using ggplotGrob inside future::future_lapply with multisession plan #6464

Open
@elgabbas

Description

@elgabbas

When using ggplot2::ggplotGrob() (and indirectly ggExtra::ggMarginal()) inside a future::future_lapply() call with plan(multisession), warnings appear related to unexpected graphics device changes. This breaks the expected isolation of child processes: child workers should not open or close graphics devices that persist outside their scope or cause side effects detectable by the parent.

Specifically, the warnings mention that a new device (usually pdf) is opened without being closed properly, or the device state changes unexpectedly during the future evaluation.

This happens even when dev.list() returns NULL before execution, and attempts to pre-open and explicitly close devices inside the future expression (e.g., using grDevices::pdf() and dev.off()) do not fully suppress these warnings.

This makes it difficult to safely use ggplot2 in parallel workflows using multisession futures, where device state consistency is critical.

Reprex:

library(ggplot2)
library(grid)
library(future.apply)

plan(multisession, workers = 2)

plot_function <- function(x) {
  p <- ggplot(data.frame(x = rnorm(100), y = rnorm(100)), aes(x, y)) +
    geom_point()
  grob <- ggplotGrob(p)
  return(grob)
}

results <- future_lapply(1:5, plot_function, future.seed = TRUE)
# Warning messages:
# 1: MultisessionFuture (‘future_lapply-1’) added, removed, or modified devices. A future expression must close
any opened devices and must not close devices it did not open. Details: 1 devices differ: index=2, before=‘NA’, after=‘pdf’ 
# 2: MultisessionFuture (‘future_lapply-2’) added, removed, or modified devices. A future expression must close
any opened devices and must not close devices it did not open. Details: 1 devices differ: index=2, before=‘NA’, after=‘pdf’ 

Opening a temporary pdf device explicitly before plotting and closing it afterwards did not fully solve the issue:

# new R session
library(ggplot2)
library(grid)
library(future.apply)

plan(multisession, workers = 2)

plot_function <- function(x) {
  # Open a temporary graphics device to absorb side effects
  tmp <- tempfile(fileext = ".pdf")
  grDevices::pdf(tmp)
  on.exit(grDevices::dev.off(), add = TRUE)
  
  # Create plot and convert to grob
  p <- ggplot(data.frame(x = rnorm(100), y = rnorm(100)), aes(x, y)) +
    geom_point()
  grob <- ggplotGrob(p)
  return(grob)
}

results <- future_lapply(1:5, plot_function, future.seed = TRUE)

# Warning messages:
# 1: MultisessionFuture (‘future_lapply-1’) added, removed, or modified devices. A future expression must close 
any opened devices and must not close devices it did not open. Details: 1 devices differ: index=2, before=‘NA’, after=‘’ 
# 2: MultisessionFuture (‘future_lapply-2’) added, removed, or modified devices. A future expression must close 
any opened devices and must not close devices it did not open. Details: 1 devices differ: index=2, before=‘NA’, after=‘’ 

What internal calls in ggplot2::ggplotGrob() or related grid functions might open a graphics device implicitly, especially in headless or parallel contexts?
Is there a recommended way to preemptively open and close devices, or to prevent implicit device openings, when using ggplot2 grobs in parallel processes?


> sessionInfo()
R version 4.5.0 (2025-04-11 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 10 x64 (build 19045)

Matrix products: default
LAPACK version 3.12.1

locale:
  [1] LC_COLLATE=English_United States.utf8  LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8 LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: Europe/Berlin
tzcode source: internal

attached base packages:
  [1] grid      stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
  [1] future.apply_1.11.3 future_1.49.0       ggplot2_3.5.2      

loaded via a namespace (and not attached):
  [1] vctrs_0.6.5        cli_3.6.5          rlang_1.1.6        generics_0.1.4    
[5] glue_1.8.0         listenv_0.9.1      scales_1.4.0       tibble_3.2.1      
[9] lifecycle_1.0.4    compiler_4.5.0     dplyr_1.1.4        codetools_0.2-20  
[13] RColorBrewer_1.1-3 pkgconfig_2.0.3    rstudioapi_0.17.1  farver_2.1.2      
[17] digest_0.6.37      R6_2.6.1           dichromat_2.0-0.1  tidyselect_1.2.1  
[21] pillar_1.10.2      parallelly_1.44.0  parallel_4.5.0     magrittr_2.0.3    
[25] tools_4.5.0        withr_3.0.2        gtable_0.3.6       globals_0.18.0    

See also: futureverse/future#788

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions