Ruby: Destructors?
Asked Answered
G

6

30

I need to occasionaly create images with rmagick in a cache dir.

To then get rid of them fast, without loosing them for the view, I want to delete the image-files while my Ruby Instance of the Image-Class get's destructed or enters the Garbage Collection.

What ClassMethod must I overwrite to feed the destructor with code?

Gentlemanly answered 10/5, 2011 at 20:29 Comment(1)
Destroy the objects explicitly - it's by design.Litterbug
C
23

You can use ObjectSpace.define_finalizer when you create the image file, and it will get invoked when the garbage man comes to collect. Just be careful not to reference the object itself in your proc, otherwise it won't be collected by the garbage man. (Won't pick up something that's alive and kicking)

class MyObject
  def generate_image
    image = ImageMagick.do_some_magick
    ObjectSpace.define_finalizer(self, proc { image.self_destruct! })
  end
end
Creekmore answered 10/5, 2011 at 21:26 Comment(3)
AFAIK (and I admit I don't have a lot of experience with it) this won't work, because procs keep an implicit reference to the self of the context they are defined in — which in this case is the same object that the finalizer is attached to, so the finalizer will prevent the object from ever being collected.Brendon
may work, but doesn't work for me as expected... I tried it out with a simple script pastie.org/1892817 -- see the pastie.. I then guessed that the object may just not being caught by the GC... but while quitting the script, you still do not get the expected outputGentlemanly
this is really a very important feature, which is missing, Mr Matz, we need destructors! :)Gentlemanly
L
28

@edgerunner's solution almost worked. Basically, you cannot create a closure in place of the define_finalizer call since that captures the binding of the current self. In Ruby 1.8, it seems that you cannot use any proc object converted (using to_proc) from a method that is bound to self either. To make it work, you need a proc object that doesn't capture the object you are defining the finalizer for.

class A
  FINALIZER = lambda { |object_id| p "finalizing %d" % object_id }

  def initialize
    ObjectSpace.define_finalizer(self, self.class.method(:finalize))  # Works in both 1.9.3 and 1.8
    #ObjectSpace.define_finalizer(self, FINALIZER)                    # Works in both
    #ObjectSpace.define_finalizer(self, method(:finalize))            # Works in 1.9.3
  end

  def self.finalize(object_id)
    p "finalizing %d" % object_id
  end

  def finalize(object_id)
    p "finalizing %d" % object_id
  end
end

a = A.new
a = nil

GC.start
Lorou answered 20/12, 2012 at 9:30 Comment(2)
Nice post. I noticed that even when calling GC.start at various points within a test of the above that ruby 1.8 does not run the finalize() method exactly in the sequence one might expect. I got predictable sequencing from ruby 1.9.3. The code above seems to work fine, but I would caution against relying upon when finalize() will be called. Note: I used version of define_finalizer(...) you left un-commented.Garlicky
it wont work then exception in parent process is thrownMalena
C
23

You can use ObjectSpace.define_finalizer when you create the image file, and it will get invoked when the garbage man comes to collect. Just be careful not to reference the object itself in your proc, otherwise it won't be collected by the garbage man. (Won't pick up something that's alive and kicking)

class MyObject
  def generate_image
    image = ImageMagick.do_some_magick
    ObjectSpace.define_finalizer(self, proc { image.self_destruct! })
  end
end
Creekmore answered 10/5, 2011 at 21:26 Comment(3)
AFAIK (and I admit I don't have a lot of experience with it) this won't work, because procs keep an implicit reference to the self of the context they are defined in — which in this case is the same object that the finalizer is attached to, so the finalizer will prevent the object from ever being collected.Brendon
may work, but doesn't work for me as expected... I tried it out with a simple script pastie.org/1892817 -- see the pastie.. I then guessed that the object may just not being caught by the GC... but while quitting the script, you still do not get the expected outputGentlemanly
this is really a very important feature, which is missing, Mr Matz, we need destructors! :)Gentlemanly
B
11

GC quirks are nice to read about, but why not properly deallocate resources according to already existing language syntax?

Let me clarify that.

class ImageDoer
  def do_thing(&block)
    image= ImageMagick.open_the_image # creates resource
    begin
      yield image # yield execution to block
    rescue
      # handle exception
    ensure
      image.destruct_sequence # definitely deallocates resource
    end
  end
end

doer= ImageDoer.new
doer.do_thing do |image|
  do_stuff_with_image # destruct sequence called if this throws
end # destruct_sequence called if execution reaches this point

Image is destroyed after the block finishes executing. Just start a block, do all the image processing inside, then let the image destroy itself. This is analogous to the following C++ example:

struct Image
{
  Image(){ /* open the image */ }
  void do_thing(){ /* do stuff with image */ }
  ~Image(){ /* destruct sequence */ }
};

int main()
{
  Image img;
  img.do_thing(); // if do_thing throws, img goes out of scope and ~Image() is called
} // special function ~Image() called automatically here
Byyourleave answered 15/5, 2013 at 19:40 Comment(3)
For me, this is the best approach.Jobi
Because the usage might not be lexically scoped. In fact lexically scoped usage is just a very tiny special use case.Nuncio
@Nuncio I don't really see your point because even if you allocate the resource once in your application entry point, use it everywhere repeatedly and deallocate within that scope you still get lexical scoping.Byyourleave
B
4

Ruby has ObjectSpace.define_finalizer to set finalizers on objects, but its use isn't exactly encouraged and it's rather limited (e.g. the finalizer can't refer to the object it is set for or else the finalizer will render the object ineligible for garbage collection).

Brendon answered 10/5, 2011 at 21:7 Comment(0)
P
2

There's really no such thing as a destructor in Ruby.

What you could do is simply clear out any files that are no longer open, or use the TempFile class which does this for you.

Update:

I previously claimed that PHP, Perl and Python do not have destructors, but this does appear to be false as igorw points out. I have not seen them used very often, though. A properly constructed destructor is essential in any allocation-based language, but in a garbage collected one it ends up being optional.

Pinkston answered 10/5, 2011 at 20:34 Comment(6)
so I should delete the outdated files by scanning their date and time hourlyGentlemanly
You can kill off unused files with a simple shell command like: find $DIRECTORY -type f -mtime +$RETAIN_PERIOD -exec rm {} \; where $DIRECTORY is the directory in question and $RETAIN_PERIOD is how long in days you want to keep them. You can also do this in Ruby with File::Stat#mtime and some glue.Pinkston
PHP, Perl and Python all have destructors. Ruby however does not.Adamek
Ah, I stand corrected then. Perl does have the END and DESTROY methods, but I've really never seen those used by ordinary people.Pinkston
I use Perl's DESTROY method quite often and it's convenient in that it's called in the context of scope similar to C++. For Python however, one would use context managers to achieve a similar effect since its destructor is called based on reference counting. Ruby, however may be a bit more manual it seems.Litterbug
php has magic method called __destruct() however those still working horrible, no matter how much i love php... also $instance = null, almost equal to destruction in php.Threepiece
G
0

There is very simple solution for your problem. Ruby design encourage you to do all actions in definite and clear way. No need for magic actions in constructor/destructor. Yes, constructors are required as a convenient way to assign initial state of object but not for "magic" actions. Let me illustrate this approach on possible solution. Goal, to keep image objects available but clean cache files of images.

# you are welcome to keep an in memory copy of the image
# GC will take care of it.
class MyImage
  RawPNG data
end

# this is a worker that does operations on the file in cache directory.
# It knows presizely when the file can be removed (generate_image_final)
# no need to wait for destructor ;)
class MyImageGenerator
  MyImage @img

  def generate_image_step1
    @image_file = ImageLib.create_file
  end
  def generate_image_step2
    ImageLib.draw @image_file
  end
  def generate_image_final
    @img=ImageLib.load_image @image_file
    delete_that_file @image_file
  end

  def getImage
    # optional check image was generated
    return @img
  end
end
Glasswork answered 28/11, 2012 at 23:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.