The following code seems to use new memory for each set of plots and does not release the old.
rm(list = ls())
library(shiny)
library(pryr)
library(ggplot2)
library(grid)
library(gridExtra)
library(startup)
totalTabs <<- 1
# Define UI for application
ui <- fluidPage(
# Application title
titlePanel("Memory Leak Test"),
sidebarLayout(
sidebarPanel(
actionButton("addTab", label = "Add Tab"),
actionButton("browser", label = "Enter Browser")
),
mainPanel(
tabsetPanel(id = "tabs") #,
# dataTableOutput("table")
)
)
)
server <- function(input, output, session) {
env <<- list()
tabNumber <<- 0
maxTab <<- 0
# mymem = data.frame(ngroups = c(NA), nplots = c(NA), p1Size = c(NA), pgrobSize = c(NA), pltSize= c(NA),
# outputSize = c(NA), memSize = c(mem_used()))
rv <- reactiveValues(
addTab = 0,
newTable = FALSE
)
onStop( function () {
if (! is.null(env[[tabNumber]])) {
cat (paste(" ", object_size(output), mem_used(), "\n"), file = stderr())
local( {
rm(list = ls())
gc()
}, envir = env[[tabNumber]])
rm(list = ls())
gc()
cat (paste(" ", object_size(output), mem_used(), "\n"), file = stderr())
# startup::restart(status = -1)
}
} )
observeEvent(input$ngroups, { rv$newTable <- rv$newTable + 1} )
observeEvent(input$nplots, { rv$newTable <- rv$newTable + 1} )
observeEvent(input$tabs, {
tabNumber <<- as.integer(input$tabs)
rv$newTable <- rv$newTable + 1
} )
observeEvent(input$browser, { browser() } )
observeEvent(input$addTab, {
maxTab <<- maxTab + 1
tabNumber <<- maxTab
if (length(env) >= tabNumber) {
rm(list = ls(env[[tabNumber]]))
gc()
} else {
env[[tabNumber]] <<- new.env()
}
env[[tabNumber]]$plt <- list()
env[[tabNumber]]$pgrob <- list()
env[[tabNumber]]$p1 <- list()
thisTab <- paste(tabNumber)
appendTab (inputId = "tabs", tabPanel(thisTab, {
sidebarLayout(
fluid = TRUE,
sidebarPanel (
sliderInput(paste0("ngroups", tabNumber),
"Number of groups:",
min = 1,
max = 3,
value = 2),
sliderInput(paste0("nplots", tabNumber),
"Number of plots in each group:",
min = 1,
max = 50,
value = 2)
), # end sidebarPanel
mainPanel (
renderUI ( {
assign("ngroups", input[[paste0("ngroups", tabNumber)]], envir = env[[tabNumber]])
assign("nplots", input[[paste0("nplots", tabNumber)]], envir = env[[tabNumber]])
rtn <- createPlots(ngroups = ngroups, nplots = nplots)
} ) # end renderUI
) # end mainPanel
) # end sidebarLayout
} ), select = TRUE) # end appendTab
rv$newTable <- rv$newTable + 1
updateTabsetPanel(session = session, inputId = "tabs", selected = thisTab )
Sys.sleep(1)
} )
createPlots <- function (ngroups, nplots) {
rtn <- local ( {
cat (paste(" ", object_size(output), mem_used(), "\n"), file = stderr())
# currentOutputSize <- object.size(output)
# currentMemorySize <- mem_used()
df <- list()
if (length(p1) > 0) {
for (g in 1:length(p1)) {
for (i in 1:length(p1[[g]])) {
p1[[g]][[i]] <- NULL
pgrob[[g]][[i]] <- NULL
}
}
}
for (g in 1:ngroups) {
p1[[g]] <- list()
pgrob[[g]] <- list()
}
for (g in 1:ngroups) {
for (i in 1:nplots) {
df[[i]] <- as.data.frame(matrix(rexp(20, rate=.1), ncol=2))
colnames(df[[i]]) <- c("x", "y")
p1[[g]][[i]] <- qplot(x,y,data = df[[i]])
pgrob[[g]][[i]] <- ggplotGrob(p1[[g]][[i]])
}
ncols = 3
if (nplots < 3) ncols <- nplots
plotname <- paste0("plot-", tabNumber, "-", g)
output[[plotname]] <- renderPlot ( {
grid <- do.call("grid.arrange", c(pgrob[[g]], name = plotname, top = paste("Group", g, "with", nplots, "Images"), ncol = ncols))
grid
} )
nrows <- ceiling(nplots/ncols)
plt[[g]] <- plotOutput(plotname, height = paste0(nrows * 100, "px"))
}
# newRow <- data.frame(ngroups = ngroups, nplots = nplots, p1Size = as.integer(object.size(p1)),
# pgrobSize = as.integer(object_size(pgrob)), pltSize = as.integer(object_size(plt)),
# outputSize = as.integer(object_size(pgrob)), memSize = as.integer(mem_used()))
# mymem <<- rbind (mymem, newRow)
cat (paste(ngroups, nplots, object_size(p1), object_size(pgrob), object_size(plt)), file = stderr())
plt
} , envir = env[[tabNumber]])
output[["table"]] <- renderDataTable(mymem)
rtn
}
}
# Run the application
shinyApp(ui = ui, server = server)
The problem seems to be that the 'output' object data is never released to be collected .
To run this app, start it and then click 'Add Tab'. Multiple tabs can be used, but for testing using one or two tabs is best. Next change the 'Number of groups' or 'Number of plots in each group.' It may take some time for large number of plots to display. Continue to change ngroups and nplots.
Clicking on 'Enter Browser' will drop you into debug mode when on Rstudio or within R.
The stderr() output is as follows:
- The first line is object_size(output) and mem_used() before any plot are created.
- The data lines are created by cat (paste(ngroups, nplots, object_size(p1), object_size(pgrob), object_size(plt)), file = stderr()) and cat (paste(" ", object_size(output), mem_used(), "\n"), file = stderr()) after the the plots are displayed. The ngroups is number of groups selected and nplots is the number plots for each group. The variable p1 is a list of plots created. The variable pgrob is a list of GROBs create using ggplotGrob on each p1 plot. The variable plt is a list of display objects passed back to the uiOutput shiny function.
- After the plots are displayed, the object_size(output) and mem_used() is displayed.
- Once the application is exited, all variables are removed, garbage collection is run, and the values of object_size(output) and mem_used() are displayed again.
Some examples of output:
Listening on http://127.0.0.1:5263
2376080 221811760
2 2 773272 392456 2392 7092784 221843712
2 25 1850776 4778160 2392 61092640 284092072
3 25 2436440 7152784 3120 140786624 374046392
3 45 3841880 12867424 3120 284032792 535600168
3 3 890480 868920 3120 293851312 534730176
3 10 1382504 2868264 3120 325881872 571541040
1 10 1148184 1915696 3120 338829016 582040176
337213896 581077008
and
Listening on http://127.0.0.1:5263
2375648 586882768
2 2 773272 391880 2392 7095328 592037232
3 2 820184 581432 3120 13711184 599485232
3 48 4052696 13724808 3120 166430312 778515936
3 49 4122968 14009032 3120 322396608 948693360
3 50 4193240 14301048 3120 481563696 1122385960
3 49 4122968 14006136 3120 637415032 1291871816
3 6 1101272 1721288 3120 656688344 1301017008
3 50 4193240 14296544 3120 815683160 1486329392
3 3 890480 869784 3120 825521496 1484103904
3 47 3982424 13441216 3120 975139168 1659234784
3 1 749912 296704 3120 978608720 1650352440
3 49 4122968 14003176 3120 1134495520 1833392088
3 5 1031024 1438088 3120 1150651400 1838880608
3 50 4193240 14294840 3120 1309717960 2024551368
3 5 1031024 1442000 3120 1325923128 2029823800
3 49 4122968 14010832 3120 1481809840 2211758264
1 49 2951768 9254856 3120 1537025936 2263134432
2 49 3256312 10486368 3120 1642513248 2380882280
3 49 4122968 14005568 3120 1811847840 2556581176
2 2 773272 394752 2392 1799622624 2557143512
2 49 3537368 11634368 3120 1905193648 2666535040
2 26 2178808 6111552 3200 1965362448 2723984872
1961390856 2719833288
As you can see the values or p1, pgrob, and plt vary as expected, but object_size(output) continues to rise. The mem_size() also generally tracks the output size. Running rm(list=ls()) does not seem to remove most of this memory.
I have run this app until I have accumulated more that 4 GB a memory. The Linux system application top
also shows this accumulation of memory.
In a previous post, when Tabs were not being used, a work around was to alway display the same number of groups, using blank plots to fill in group plots above ngroups. This was tried here, but does ot work with tabs.
It is possible that I have a bug in my code, but I have tried every thing I can think of and the memory leak is still there. Automatic garbage collection is nice when it works, but a nightmare when it doesn't.