In Files

Parent

Lockfile

Attributes

debug[RW]
dont_clean[RW]
dont_sweep[RW]
dont_use_lock_id[RW]
max_age[RW]
max_sleep[RW]
min_sleep[RW]
poll_max_sleep[RW]
poll_retries[RW]
refresh[RW]
retries[RW]
sleep_inc[RW]
suspend[RW]
timeout[RW]
basename[R]
clean[R]
debug[RW]
debug?[RW]
dirname[R]
dont_clean[R]
dont_sweep[R]
dont_use_lock_id[R]
klass[R]
locked[R]
locked?[R]
max_age[R]
max_sleep[R]
min_sleep[R]
opts[R]
path[R]
poll_max_sleep[R]
poll_retries[R]
refresh[R]
refresher[R]
retries[R]
sleep_inc[R]
suspend[R]
thief[R]
thief?[R]
timeout[R]

Public Class Methods

create(path, *a, &b) click to toggle source
# File lib/lockfile.rb, line 139
def Lockfile.create(path, *a, &b)
  opts = {
    'retries' => 0,
    'min_sleep' => 0,
    'max_sleep' => 1,
    'sleep_inc' => 1,
    'max_age' => nil,
    'suspend' => 0,
    'refresh' => nil,
    'timeout' => nil,
    'poll_retries' => 0,
    'dont_clean' => true,
    'dont_sweep' => false,
    'dont_use_lock_id' => true,
  }
  begin
    new(path, opts).lock
  rescue LockError
    raise Errno::EEXIST, path
  end
  open(path, *a, &b)
end
init() click to toggle source
# File lib/lockfile.rb, line 86
def init
  @retries          = DEFAULT_RETRIES
  @max_age          = DEFAULT_MAX_AGE
  @sleep_inc        = DEFAULT_SLEEP_INC
  @min_sleep        = DEFAULT_MIN_SLEEP
  @max_sleep        = DEFAULT_MAX_SLEEP
  @suspend          = DEFAULT_SUSPEND
  @timeout          = DEFAULT_TIMEOUT
  @refresh          = DEFAULT_REFRESH
  @dont_clean       = DEFAULT_DONT_CLEAN
  @poll_retries     = DEFAULT_POLL_RETRIES
  @poll_max_sleep   = DEFAULT_POLL_MAX_SLEEP
  @dont_sweep       = DEFAULT_DONT_SWEEP
  @dont_use_lock_id = DEFAULT_DONT_USE_LOCK_ID

  @debug          = DEFAULT_DEBUG

  STDOUT.sync = true if @debug
  STDERR.sync = true if @debug
end
new(path, opts = {}, &block) click to toggle source
# File lib/lockfile.rb, line 162
def initialize(path, opts = {}, &block)
  @klass = self.class
  @path  = path
  @opts  = opts

  @retries          = getopt 'retries'          , @klass.retries
  @max_age          = getopt 'max_age'          , @klass.max_age
  @sleep_inc        = getopt 'sleep_inc'        , @klass.sleep_inc
  @min_sleep        = getopt 'min_sleep'        , @klass.min_sleep
  @max_sleep        = getopt 'max_sleep'        , @klass.max_sleep
  @suspend          = getopt 'suspend'          , @klass.suspend
  @timeout          = getopt 'timeout'          , @klass.timeout
  @refresh          = getopt 'refresh'          , @klass.refresh
  @dont_clean       = getopt 'dont_clean'       , @klass.dont_clean
  @poll_retries     = getopt 'poll_retries'     , @klass.poll_retries
  @poll_max_sleep   = getopt 'poll_max_sleep'   , @klass.poll_max_sleep
  @dont_sweep       = getopt 'dont_sweep'       , @klass.dont_sweep
  @dont_use_lock_id = getopt 'dont_use_lock_id' , @klass.dont_use_lock_id
  @debug            = getopt 'debug'            , @klass.debug

  @sleep_cycle = SleepCycle.new @min_sleep, @max_sleep, @sleep_inc 

  @clean    = @dont_clean ? nil : lambda{ File.unlink @path rescue nil }
  @dirname  = File.dirname @path
  @basename = File.basename @path
  @thief    = false
  @locked   = false
  @refrsher = nil

  lock(&block) if block
end
version() click to toggle source
# File lib/lockfile.rb, line 10
def Lockfile.version() Lockfile::VERSION end

Public Instance Methods

again!() click to toggle source
Alias for: try_again!
alive?(pid) click to toggle source
# File lib/lockfile.rb, line 339
def alive? pid
  pid = Integer("#{ pid }")
  begin
    Process.kill 0, pid
    true
  rescue Errno::ESRCH
    false
  end
end
attempt() click to toggle source
# File lib/lockfile.rb, line 515
def attempt
  ret = nil
  loop{ break unless catch('attempt'){ ret = yield } == 'try_again' }
  ret
end
create(path) click to toggle source
# File lib/lockfile.rb, line 479
def create(path)
  umask = nil 
  f = nil
  begin
    umask = File.umask 022
    f = open path, File::WRONLY|File::CREAT|File::EXCL, 0644
  ensure
    File.umask umask if umask
  end
  return(block_given? ? begin; yield f; ensure; f.close; end : f)
end
create_tmplock() click to toggle source
# File lib/lockfile.rb, line 419
def create_tmplock
  tmplock = tmpnam @dirname
  begin
    create(tmplock) do |f|
      unless dont_use_lock_id
        @lock_id = gen_lock_id
        dumped = dump_lock_id
        trace{"lock_id <\n#{ @lock_id.inspect }\n>"}
        f.write dumped 
        f.flush
      end
      yield f
    end
  ensure
    begin; File.unlink tmplock; rescue Errno::ENOENT; end if tmplock
  end
end
dump_lock_id(lock_id = @lock_id) click to toggle source
# File lib/lockfile.rb, line 453
def dump_lock_id(lock_id = @lock_id)
  "host: %s\npid: %s\nppid: %s\ntime: %s\n" %
    lock_id.values_at('host','pid','ppid','time')
end
errmsg(e) click to toggle source
# File lib/lockfile.rb, line 511
def errmsg(e)
  "%s (%s)\n%s\n" % [e.class, e.message, e.backtrace.join("\n")]
end
gen_lock_id() click to toggle source
# File lib/lockfile.rb, line 437
def gen_lock_id
  Hash[
    'host' => "#{ HOSTNAME }",
    'pid' => "#{ Process.pid }",
    'ppid' => "#{ Process.ppid }",
    'time' => timestamp, 
  ]
end
getopt(key, default = nil) click to toggle source
# File lib/lockfile.rb, line 495
def getopt(key, default = nil)
  [ key, key.to_s, key.to_s.intern ].each do |k|
    return @opts[k] if @opts.has_key?(k)
  end
  return default
end
give_up!() click to toggle source
# File lib/lockfile.rb, line 526
def give_up!
  throw 'attempt', 'give_up'
end
load_lock_id(buf) click to toggle source
# File lib/lockfile.rb, line 458
def load_lock_id(buf)
  lock_id = {}
  kv = /([^:]+):(.*)/
  buf.each_line do |line|
    m = kv.match line
    k, v = m[1], m[2]
    next unless m and k and v 
    lock_id[k.strip] = v.strip
  end
  lock_id
end
lock() click to toggle source
# File lib/lockfile.rb, line 194
def lock
  raise StackingLockError, "<#{ @path }> is locked!" if @locked

  sweep unless @dont_sweep

  ret = nil 

  attempt do
    begin
      @sleep_cycle.reset
      create_tmplock do |f|
        begin
          Timeout.timeout(@timeout) do
            tmp_path = f.path
            tmp_stat = f.lstat
            n_retries = 0
            trace{ "attempting to lock <#{ @path }>..." }
            begin
              i = 0
              begin
                trace{ "polling attempt <#{ i }>..." }
                begin
                  File.link tmp_path, @path
                rescue Errno::ENOENT
                  try_again!
                end
                lock_stat = File.lstat @path
                raise StatLockError, "stat's do not agree" unless
                  tmp_stat.rdev == lock_stat.rdev and tmp_stat.ino == lock_stat.ino 
                trace{ "aquired lock <#{ @path }>" }
                @locked = true
              rescue => e
                i += 1
                unless i >= @poll_retries 
                  t = [rand(@poll_max_sleep), @poll_max_sleep].min
                  trace{ "poll sleep <#{ t }>..." }
                  sleep t
                  retry
                end
                raise
              end

            rescue => e
              n_retries += 1
              trace{ "n_retries <#{ n_retries }>" }
              case validlock?
                when true
                  raise MaxTriesLockError, "surpased retries <#{ @retries }>" if 
                    @retries and n_retries >= @retries 
                  trace{ "found valid lock" }
                  sleeptime = @sleep_cycle.next 
                  trace{ "sleep <#{ sleeptime }>..." }
                  sleep sleeptime
                when false
                  trace{ "found invalid lock and removing" }
                  begin
                    File.unlink @path
                    @thief = true
                    warn "<#{ @path }> stolen by <#{ Process.pid }> at <#{ timestamp }>"
                    trace{ "i am a thief!" }
                  rescue Errno::ENOENT
                  end
                  trace{ "suspending <#{ @suspend }>" }
                  sleep @suspend
                when nil
                  raise MaxTriesLockError, "surpased retries <#{ @retries }>" if 
                    @retries and n_retries >= @retries 
              end
              retry
            end # begin
          end # timeout 
        rescue Timeout::Error
          raise TimeoutLockError, "surpassed timeout <#{ @timeout }>"
        end # begin
      end # create_tmplock

      if block_given?
        stolen = false
        @refresher = (@refresh ? new_refresher : nil) 
        begin
          begin
            ret = yield @path
          rescue StolenLockError
            stolen = true
            raise
          end
        ensure
          begin
            @refresher.kill if @refresher and @refresher.status
            @refresher = nil
          ensure
            unlock unless stolen
          end
        end
      else
        @refresher = (@refresh ? new_refresher : nil)
        ObjectSpace.define_finalizer self, @clean if @clean
        ret = self
      end
    rescue Errno::ESTALE, Errno::EIO => e
      raise(NFSLockError, errmsg(e)) 
    end
  end

  return ret
end
new_refresher() click to toggle source
# File lib/lockfile.rb, line 366
def new_refresher
  Thread.new(Thread.current, @path, @refresh, @dont_use_lock_id) do |thread, path, refresh, dont_use_lock_id|
    loop do 
      begin
        touch path
        trace{"touched <#{ path }> @ <#{ Time.now.to_f }>"}
        unless dont_use_lock_id
          loaded = load_lock_id(IO.read(path))
          trace{"loaded <\n#{ loaded.inspect }\n>"}
          raise unless loaded == @lock_id 
        end
        sleep refresh
      rescue Exception => e
        trace{errmsg e}
        thread.raise StolenLockError
        Thread.exit
      end
    end
  end
end
sweep() click to toggle source
# File lib/lockfile.rb, line 301
def sweep
  begin
    glob = File.join(@dirname, ".*lck")
    paths = Dir[glob]
    paths.each do |path|
      begin
        basename = File.basename path
        pat = /^\s*\.([^_]+)_([^_]+)/
        if pat.match(basename)
          host, pid = $1, $2
        else
          next
        end
        host.gsub!(/^\.+|\.+$/,'')
        quad = host.split /\./
        host = quad.first
        pat = /^\s*#{ host }/
        if pat.match(HOSTNAME) and /^\s*\d+\s*$/.match(pid)
          unless alive?(pid)
            trace{ "process <#{ pid }> on <#{ host }> is no longer alive" }
            trace{ "sweeping <#{ path }>" }
            FileUtils.rm_f path
          else
            trace{ "process <#{ pid }> on <#{ host }> is still alive" }
            trace{ "ignoring <#{ path }>" }
          end
        else
          trace{ "ignoring <#{ path }> generated by <#{ host }>" }
        end
      rescue
        next
      end
    end
  rescue => e
    warn(errmsg(e))
  end
end
timestamp() click to toggle source
# File lib/lockfile.rb, line 446
def timestamp
  time = Time.now
  usec = time.usec.to_s
  usec << '0' while usec.size < 6
  "#{ time.strftime('%Y-%m-%d %H:%M:%S') }.#{ usec }"
end
tmpnam(dir, seed = File.basename($0)) click to toggle source
# File lib/lockfile.rb, line 470
def tmpnam(dir, seed = File.basename($0))
  pid = Process.pid
  time = Time.now
  sec = time.to_i
  usec = time.usec
  "%s%s.%s_%d_%s_%d_%d_%d.lck" % 
    [dir, File::SEPARATOR, HOSTNAME, pid, seed, sec, usec, rand(sec)]
end
to_s() click to toggle source
Alias for: to_str
to_str() click to toggle source
# File lib/lockfile.rb, line 502
def to_str
  @path
end
Also aliased as: to_s
touch(path) click to toggle source
# File lib/lockfile.rb, line 491
def touch(path)
  FileUtils.touch path
end
trace(s = nil) click to toggle source
# File lib/lockfile.rb, line 507
def trace(s = nil)
  STDERR.puts((s ? s : yield)) if @debug
end
try_again!() click to toggle source
# File lib/lockfile.rb, line 521
def try_again!
  throw 'attempt', 'try_again'
end
Also aliased as: again!
uncache(file) click to toggle source
# File lib/lockfile.rb, line 401
def uncache file 
  refresh = nil
  begin
    is_a_file = File === file
    path = (is_a_file ? file.path : file.to_s) 
    stat = (is_a_file ? file.stat : File.stat(file.to_s)) 
    refresh = tmpnam(File.dirname(path))
    File.link path, refresh
    File.chmod stat.mode, path
    File.utime stat.atime, stat.mtime, path
  ensure 
    begin
      File.unlink refresh if refresh
    rescue Errno::ENOENT
    end
  end
end
unlock() click to toggle source
# File lib/lockfile.rb, line 349
def unlock
  raise UnLockError, "<#{ @path }> is not locked!" unless @locked

  @refresher.kill if @refresher and @refresher.status
  @refresher = nil

  begin
    File.unlink @path
  rescue Errno::ENOENT
    raise StolenLockError, @path
  ensure
    @thief = false
    @locked = false
    ObjectSpace.undefine_finalizer self if @clean
  end
end
validlock?() click to toggle source
# File lib/lockfile.rb, line 387
def validlock?
  if @max_age
    uncache @path rescue nil
    begin
      return((Time.now - File.stat(@path).mtime) < @max_age)
    rescue Errno::ENOENT
      return nil 
    end
  else
    exist = File.exist?(@path)
    return(exist ? true : nil)
  end
end
version() click to toggle source
# File lib/lockfile.rb, line 11
def version() Lockfile::VERSION end

[Validate]

Generated with the Darkfish Rdoc Generator 2.