2006-08-07

perl-like lock for ruby

cdfh was asking in #ruby-lang about a locking library that allows one to do:
 
lock(an_obj) { ..... }

similar to the lock library in perl.

"That won't not that hard to build", I thought. I did the first version in about 5 minutes. Then thought that it would be nice to make it robust to object_id wrap-around and also to discard unnecessary records associate to an_obj when an_obj has been garbage collected.
 
require 'thread'
require 'weakref'


class Lock
  Record = Struct.new("Record", :weakref, :mutex)

  def initialize(auto_cleanup_per_record = 1000)
    @mutex = Mutex.new
    @obj_records = {}
    @create_count = 0
    @auto_cleanup_per_record = auto_cleanup_per_record
  end

  def lock(what, &block)
    record = acquire_obj_record(what)
    record.mutex.synchronize {
      if block_given?
        block.call
      end
    }
  end
  

  private

  def cleanup_proc
    lambda {|id|
      @mutex.synchronize {
        to_delete = []
        @obj_records.each_pair{|k,v|
          if not v.weakref.weakref_alive?
            to_delete << k
          end
        }
        to_delete.each{|id|
          @obj_records.delete(id)
        }
        #puts "Cleanup id: #{to_delete.join(",")}. Size: #{@obj_records.size}: #{@obj_records.keys.join(",")}"
      }
    }
  end

  def acquire_obj_record(what)
    @mutex.synchronize {
      if record = @obj_records[what.object_id] and record.weakref.weakref_alive? 
        record
      else
        ObjectSpace.define_finalizer(what, cleanup_proc)
        @obj_records[what.object_id] = Record.new(WeakRef.new(what), Mutex.new)
      end
    }
  end

  public
  
  @instance = Lock.new
  def self.lock(what, &block)
    @instance.lock(what, &block)
  end
  def self.cleanup(verbose=false)
    @instance.cleanup(verbose)
  end
end



if $0 == __FILE__
  module Test
    def self.test1(finalizer_block)
      puts "Test1"
      s="str"*1024*1024
      ObjectSpace.define_finalizer(s, finalizer_block)
      puts s.object_id
      
      t1 = Thread.new {
        Lock.lock(s) {
          4.times {
            puts "Thread1"
            sleep(0.25)
          }
        }
      }
      
      t2 = Thread.new {
        puts "Thread2 started"
        Lock.lock(s) {
          4.times { 

            puts "Thread2"
            sleep(0.25)
          }
        }
      }
      
      t1.join
      t2.join
    end

    def self.test2(finalizer_block)
      puts "Test2"
      s="noo"*1024*1024
      puts s.object_id
      ObjectSpace.define_finalizer(s, finalizer_block)
      Lock.lock(s)
    end

    def self.test3(finalizer_block)
      puts "Test3"
      10.times {
        s="noo"*1024*1024
        puts s.object_id
        ObjectSpace.define_finalizer(s, finalizer_block)
        Lock.lock(s)
      }
    end
  end



  Test.test1(lambda {|id| puts "Finalized: #{id}"})
  GC.start
  puts "GC invoked"

  Test.test2(lambda {|id| puts "Finalized: #{id}"})
  GC.start
  puts "GC invoked"

  Test.test3(lambda {|id| puts "Finalized: #{id}"})
  GC.start
  puts "GC invoked"
end


=begin
/mnt/vg0.home/ysantoso/tmp/ruby-postgres/tests $ ruby /tmp/lock.rb 
Test1
-604990708
Thread1
Thread2 started
Thread1
Thread1
Thread1
Thread2
Thread2
Thread2
Thread2
GC invoked
Test2
-610787788
GC invoked
Test3
-610788148
-610953348
-615730956
-615809476
-615887996
Finalized: -615730956
Finalized: -610953348
-615902566
-615981086
-615697446
Finalized: -615809476
Finalized: -615887996
Finalized: -615902566
-614121002
-614199522
Finalized: -614121002
Cleanup id: -615809476,-615730956,-610953348,-615902566,-615887996. Size: 7: -614199522,-615697446,-615981086,-610787788,-614121002,-610788148,-604990708
Finalized: -614199522
Cleanup id: -614121002. Size: 6: -614199522,-615697446,-615981086,-610787788,-610788148,-604990708
Finalized: -615697446
Cleanup id: -614199522. Size: 5: -615697446,-615981086,-610787788,-610788148,-604990708
Finalized: -615981086
Cleanup id: -615697446. Size: 4: -615981086,-610787788,-610788148,-604990708
Finalized: -610787788
Cleanup id: -615981086. Size: 3: -610787788,-610788148,-604990708
Finalized: -610788148
Cleanup id: -610787788. Size: 2: -610788148,-604990708
GC invoked
Finalized: -604990708

=end 
 
 
(originally from http://microjet.ath.cx/WebWiki/2006.08.07_LockLibraryForRuby.html)