7

I need to clean up an R instance to return it to its on-launch virginal state. So far, what I'm doing is:

On launch, record the loaded packages and namespaces

original_packages <- grep('^package:', search(), value = TRUE)
original_namespaces <- loadedNamespaces()

When I need to flush the instance, detach each loaded package that was not there at launch:

for (pkg in grep('^package:', search(), value = TRUE)) {
    if (! pkg %in% original_packages){
        detach(pkg, unload=TRUE, force=TRUE, character.only=TRUE)
    }
}

The problem is that if I have loaded a package with a bunch of imported namespaces, such as ggplot2, those namespaces stay loaded, and I have to unload them in order of import, from high-level down. Just unloading them blindly doesn't work, as I get "namespace ‘x’ is imported by ‘y’, ‘z’ so cannot be unloaded" errors.

Here is reproducible example:

original_packages <- grep('^package:', search(), value = TRUE)
original_namespaces <- loadedNamespaces()

library(ggplot2)
library(plyr)

loadedNamespaces()

for (pkg in grep('^package:', search(), value = TRUE)) {
    if (! pkg %in% original_packages){
        detach(pkg, unload=TRUE, force=TRUE, character.only=TRUE)
    }
}

for (ns in loadedNamespaces()) {
    if (! ns %in% original_namespaces){
        unloadNamespace(ns)
    }
}

Is there some way to figure out the namespace import hierarchy? If so, then I should just be able to order the last loop correctly...

Tarek
  • 2,041
  • 3
  • 15
  • 20
  • 4
    I gave up on this method a long time ago. AFAIK, the documentation for `detach` says it's not guaranteed to work. The only guaranteed way is to start a fresh session. – Andrie Jun 27 '12 at 17:48
  • Yeah, unfortunately I can't start a fresh session under rpy2 AFAICT - this is for a web app that has a persistent R session in its current design. – Tarek Jun 27 '12 at 18:35
  • 1
    Following up on @Andrie's comment, the last few comments on the answer to [this SO question](http://stackoverflow.com/questions/11004018/how-can-a-non-imported-method-in-a-not-attached-package-be-found-by-calls-to-fun) get at one of the reasons that simply `detach`ing won't reliably restore your session to its original state. – Josh O'Brien Jun 27 '12 at 18:44
  • Yeah, I've pretty much given up on `detach()`. Right now I'm trying to put together a solution that uses `getNamespaceInfo()` to determine the imports, and recursively goes through those to determine the import hierarchy. Then, `unloadNamespace()` from the bottom up. – Tarek Jun 27 '12 at 19:12

2 Answers2

0

Ok, I've thrown together a hacky solution to an admittedly hacky need. Any advice on how to do this better would be appreciated. In particular, I'm not too happy with the <<- assignment of the global namespace_depths object.

original_packages <- grep('^package:', search(), value = TRUE)
original_namespaces <- loadedNamespaces()

library(ggplot2)
library(plyr)

loadedNamespaces()

new_packages <- Filter(function(pkg) { ! pkg %in% original_packages }, grep('^package:', search(), value = TRUE))

new_namespaces <- Filter(function(ns) { ! ns %in% original_namespaces }, loadedNamespaces())

get_imports <- function(ns, depth) {

    imports <- Filter(function(ns) { ! ns %in% original_namespaces }, names(getNamespaceInfo(ns, 'imports')))
    if (length(imports) == 0 ) {
        if ( is.null(namespace_depths[[ns]]) || namespace_depths[[ns]] < depth){
            namespace_depths[[ns]] <<- depth
        }
        return()
    }
    for (imported_ns in imports){
        get_imports(imported_ns, depth + 1)
    }
    if ( is.null(namespace_depths[[ns]]) || namespace_depths[[ns]] < depth){
        namespace_depths[[ns]] <<- depth
    }
}

namespace_depths <- list()
sapply(new_namespaces, get_imports, 0)

for (ns in names(namespace_depths)[order(unlist(namespace_depths))]) {
    if (! ns %in% original_namespaces){
        unloadNamespace(ns)
    }
}

for (pkg in new_packages){
    detach(pkg, unload=TRUE, force=TRUE, character.only=TRUE)
}
Tarek
  • 2,041
  • 3
  • 15
  • 20
  • FWIW, in the example I linked above, this *still* doesn't get rid of methods from previously attached packages (in that case `reorder.factor()` from `gmodels`) that have been registered in the `.__S3MethodsTable__.`s of still-attached packages (in that case `stats`). – Josh O'Brien Jun 27 '12 at 19:38
  • Thanks for that clarification, Josh. Luckily, this is good enough for what I'm doing, and sgibb's answer is way simpler than mine. – Tarek Jun 27 '12 at 22:25
0

As @Josh O'Brien mentioned it isn't possible to get a clean environment by detach or unload namespaces.

But to answer your question here is a simple approach to unload all namespaces in correct order by using tools:::dependsOnPkgs:

## params: originalNamespaces is a list of namespaces you want to keep
cleanNamespaces <- function(originalNamespaces) {

    ## which namespaces should be removed?
    ns <- setdiff(loadedNamespaces(), originalNamespaces)

    ## get dependency list
    dep <- unlist(lapply(ns, tools:::dependsOnPkgs))

    ## append namespaces to guarantee to fetch namespaces with 
    ## no reverse dependencies
    ns <- c(dep, ns)

    ## get namespace names in correct order to unload without errors
    ns <- names(sort(table(ns), decreasing=TRUE))

    ## only unload namespaces which are attached
    ns <- ns[ns %in% loadedNamespaces()]

    ## unload namespaces
    invisible(sapply(ns, unloadNamespace))
}
sgibb
  • 25,396
  • 3
  • 68
  • 74