Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it necessary to close StringIO in ruby?

Tags:

ruby

Do we need to close StringIO objects after usage in Ruby in order to free resources, like we do with the real IO objects?

obj = StringIO.new "some string"
#...
obj.close # <--- Do we need to close it?

Refining my question

Closing File object is necessary because it will close the file descriptor. The number of opened files is limited in the OS, and that's why it is necessary to close File. But, if I understand correctly, StringIO is an abstraction in memory. Do we need to close it?

like image 717
Evgenii Avatar asked Apr 11 '12 13:04

Evgenii


People also ask

What is Stringio in Ruby?

Pseudo I/O on String object. Commonly used to simulate `$stdio` or `$stderr`

What is the syntax to close file in Ruby?

We can create a new File using File. new() method for reading, writing or for both, according to the mode string. we can use fileobject. close() method to close that file.


3 Answers

  • StringIO#close does not free any resources or drop its reference to the accumulated string. Therefore calling it has no effect upon resource usage.

  • Only StringIO#finalize, called during garbage collection, frees the reference to the accumulated string so that it can be freed (provided the caller does not retain its own reference to it).

  • StringIO.open, which briefly creates a StringIO instances, does not keep a reference to that instance after it returns; therefore that StringIO's reference to the accumulated string can be freed (provided the caller does not retain its own reference to it).

  • In practical terms, there is seldom a need to worry about a memory leak when using StringIO. Just don't hang on to references to StringIO once you're done with them and all will be well.


Diving into the source

The only resource used by a StringIO instance is the string it is accumulating. You can see that in stringio.c (MRI 1.9.3); here we see the structure that holds a StringIO's state:

static struct StringIO *struct StringIO {
    VALUE string;
    long pos;
    long lineno;
    int flags;
    int count;
};

When a StringIO instance is finalized (that is, garbage collected), its reference to the string is dropped so that the string may be garbage collected if there are no other references to it. Here's the finalize method, which is also called by StringIO#open(&block) in order to close the instance.

static VALUE
strio_finalize(VALUE self)
{
    struct StringIO *ptr = StringIO(self);
    ptr->string = Qnil;
    ptr->flags &= ~FMODE_READWRITE;
    return self;
}

The finalize method is called only when the object is garbage collected. There is no other method of StringIO which frees the string reference.

StringIO#close just sets a flag. It does not free the reference to the accumulated string or in any other way affect resource usage:

static VALUE
strio_close(VALUE self)
{   
    struct StringIO *ptr = StringIO(self);
    if (CLOSED(ptr)) {
        rb_raise(rb_eIOError, "closed stream");
    }
    ptr->flags &= ~FMODE_READWRITE;
    return Qnil;
}

And lastly, when you call StringIO#string, you get a reference to the exact same string that the StringIO instance has been accumulating:

static VALUE
strio_get_string(VALUE self)
{   
    return StringIO(self)->string;
}

How to leak memory when using StringIO

All of this means that there is only one way for a StringIO instance to cause a resource leak: You must not close the StringIO object, and you must keep it around longer than you keep the string you got when you called StringIO#string. For example, imagine a class having a StringIO object as an instance variable:

class Leaker

  def initialize
    @sio = StringIO.new
    @sio.puts "Here's a large file:"
    @sio.puts
    @sio.write File.read('/path/to/a/very/big/file')
  end

  def result
    @sio.string
  end

end

Imagine that the user of this class gets the result, uses it briefly, and then discards it, and yet keeps a reference to the instance of Leaker. You can see that the Leaker instance retains a reference to the result via the un-closed StringIO instance. This could be a problem if the file is very large, or if there are many extant instance of Leaker. This simple (and deliberately pathological) example can be fixed by simply not keeping the StringIO as an instance variable. When you can (and you almost always can), it's better to simply throw away the StringIO object than to go through the bother of closing it explicitly:

class NotALeaker

  attr_reader :result

  def initialize
    sio = StringIO.new
    sio.puts "Here's a large file:"
    sio.puts
    sio.write File.read('/path/to/a/very/big/file')
    @result = sio.string
  end

end

Add to all of this that these leaks only matter when the strings are large or the StringIO instances numerous and the StringIO instance is long lived, and you can see that explicitly closing StringIO is seldom, if ever, needed.

like image 145
Wayne Conrad Avatar answered Nov 04 '22 14:11

Wayne Conrad


When you close a File, it is important as the system has a limited amount of descriptors (assuming your on a UNIX, I have no clue what Windows does). With StringIO, there is another resource at stake: Memory. If the StringIO object has a lot to store, there is going to be a lot of memory claimed from the heap. On the other hand, the garbage collector does always close IO objects that it claims, but that assumes that you have an elegant way to put your object out of scope. Also, on a heavily loaded system, physical RAM is much more valuable then file descriptors. On my Linux box, 178203 is the max file descriptors. I doubt that you could reach that.

like image 23
Linuxios Avatar answered Nov 04 '22 12:11

Linuxios


In response to the other answers here: calling close won't help you save memory.

require "stringio"
sio = StringIO.new
sio.print("A really long string")
sio.close
sio.string # => "A really long string"

"A really long string" will stay around as long as sio does, close or no close.

So why does StringIO have a close method? Duck typing. Providing close, and throwing an IOError if you try to read or write from it, ensures it acts like a real File object would. This is useful if you're using it as a mock object while unit testing.

like image 4
Andrew Grimm Avatar answered Nov 04 '22 14:11

Andrew Grimm