Repository: ipython/ipython

Issue:
In rmagic, images aren't always closed
If a lot of R code is called which raises an RInterpreterError eventually I run into a 'too many open devices error'

I fixed it by wrapping the code in a try/finally block to make sure that dev.off() gets called

``` python
            try:
                self.r('png("%s/Rplots%%03d.png",%s)' % (tmpd.replace('\\', '/'), png_args))

                text_output = ''
                try:
                    if line_mode:
                        for line in code.split(';'):
                            text_result, result, visible = self.eval(line)
                            text_output += text_result
                        if text_result:
                            # the last line printed something to the console so we won't return it
                            return_output = False
                    else:
                        text_result, result, visible = self.eval(code)
                        text_output += text_result
                        if visible:
                            old_writeconsole = ri.get_writeconsole()
                            ri.set_writeconsole(self.write_console)
                            ro.r.show(result)
                            text_output += self.flush()
                            ri.set_writeconsole(old_writeconsole)

                except RInterpreterError as e:
                    print(e.stdout)
                    if not e.stdout.endswith(e.err):
                        print(e.err)
                    rmtree(tmpd)
                    return
            finally:
                self.r('dev.off()')
```



Discussion:
Pinging @davclark, who's working on moving the rmagic code to rpy2. Dav, assuming this change makes sense, can you ensure that it gets applied to each working copy of the code?

It seems like using a context handler would be more natural for things like this:

``` python
with rdev:
    code
```

where the context handler exit would do the dev.off().

I agree that this sounds like a good choice for context handler. rpy2 is happily dropping support for 2.6.x and lower, so no worries there. I'm unsure of the version support for IPython, but with statements can be enabled in python 2.5, and are included in 2.6. If someone implements, please ping me on this! Also, probably worth keeping @lgautier (author of rpy2) in the loop.

We've already dropped support for 2.6 - master only support 2.7 and 3.3.

(@davclark : thanks).

@jasongrout : a context handler is an interesting idea, but R's graphical devices are strange beasts. They cannot be manipulated directly (There is an attempt at having "graphical device" objects in rpy2, but that currently only for custom devices). At first sight, I'd think that it would be difficult to have them without having something relatively brittle.

The R graphical devices behave like follows (R code)

```
# initially no opened devices
> dev.list()
NULL
# create a device that outputs the figure into a PNG file
> res <- png("foo.png")
# the function png() only has side effects (nothing returned)
> res 
NULL
# the result is now one device in the list of opened devices
> dev.list()
png
2
# let's open a second device
> res <- png("bar.png")
# now we have two devices. the latest device opened is now the active plotting device
> dev.list()
png png
    2     3
# this can be checked by asking what the plotting device
> dev.cur()
png
3
# now you might thing that we have handle to device objects...
# unfortunately this is not the case. dev.list() is only returning a "named" vector of integers
> str(dev.list())
 Named int [1:2] 2 3
 - attr(*, "names")= chr [1:2] "png" "png"
# changing the currently active graphical device can be achieved with either
# dev.set(). It is cycling through the elements in the list
# (when reaching the last element, the next element becomes the first one)
> dev.next()
png
2
> dev.set()
png
3
```

Now with a context manager we cannot not ensure that the device opened is the one we would be closing. The integers are recycled as devices are closed and created (see below). In other words, the context can change without having the manager quite aware of it. It is possible to achieve similarly pathological situations with a context manager in pure Python, but I'd argue that in that case this is the programmers own fault. Here 3rd party code (the R code run) could break everything.

```
> graphics.off() # close all graphics
> png("foo.png")
> pdf("bar.pdf")
> dev.list()
png pdf
    2     3
> # now plotting anything goes into #3 (pdf).
> dev.close()
> # now plotting goes into #2
> png("bar.png")
> # now device #3 is the active plotting device, and it is a png
```

Nothing definitive that cannot be discussed but, as much as this is a seducing idea, I think that a context manager for graphical devices for rpy2 would be more efforts (and potential issues) than it is worth it (unless one completely controls what the R side is doing... so not for general use in rpy2).

@lgautier: my point was that the try/finally would probably be more "pythonic" as a context manager with an exit.  How is the situation improved with a try/finally over a context manager?  In the context exit, you can execute `self.r('dev.off()')`, just like in your finally statement, and R takes care of closing whatever is open.

To clarify, you're not using try to catch exceptions here, but just to make sure some code runs after a block of code.  That's exactly what context managers are for.

Issue created here:

https://bitbucket.org/lgautier/rpy2/issue/162/capture-results-of-ipython-issue-4408

Note, this may be an issue best addressed in rpy2 (rmagic in ipython is meant for eventual deprecation, assuming we get it right in rpy2).


Resolving patch:
diff --git a/IPython/extensions/rmagic.py b/IPython/extensions/rmagic.py
--- a/IPython/extensions/rmagic.py
+++ b/IPython/extensions/rmagic.py
@@ -624,8 +624,8 @@ def R(self, line, cell=None, local_ns=None):
                 print(e.err)
             rmtree(tmpd)
             return
-
-        self.r('dev.off()')
+        finally:
+            self.r('dev.off()')
 
         # read out all the saved .png files
 


Test patch: