class Lockfile
Constants
- DEFAULT_DEBUG
- DEFAULT_DONT_CLEAN
- DEFAULT_DONT_SWEEP
- DEFAULT_DONT_USE_LOCK_ID
- DEFAULT_MAX_AGE
- DEFAULT_MAX_SLEEP
- DEFAULT_MIN_SLEEP
- DEFAULT_POLL_MAX_SLEEP
- DEFAULT_POLL_RETRIES
- DEFAULT_REFRESH
- DEFAULT_RETRIES
- DEFAULT_SLEEP_INC
- DEFAULT_SUSPEND
- DEFAULT_TIMEOUT
- HOSTNAME
- VERSION
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 143 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
description()
click to toggle source
# File lib/lockfile.rb, line 13 def Lockfile.description 'a ruby library for creating perfect and NFS safe lockfiles' end
init()
click to toggle source
# File lib/lockfile.rb, line 90 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 166 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
alive?(pid)
click to toggle source
# File lib/lockfile.rb, line 357 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 533 def attempt ret = nil loop{ break unless catch('attempt'){ ret = yield } == 'try_again' } ret end
create(path) { |f;| ... }
click to toggle source
# File lib/lockfile.rb, line 497 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() { |f| ... }
click to toggle source
# File lib/lockfile.rb, line 437 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 471 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 529 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 455 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 513 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 544 def give_up! throw 'attempt', 'give_up' end
load_lock_id(buf)
click to toggle source
# File lib/lockfile.rb, line 476 def load_lock_id(buf) lock_id = {} kv = %r/([^:]+):(.*)/o 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() { |path| ... }
click to toggle source
# File lib/lockfile.rb, line 212 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 384 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 319 def sweep begin glob = File.join(@dirname, ".*lck") paths = Dir[glob] paths.each do |path| begin basename = File.basename path pat = %r/^\s*\.([^_]+)_([^_]+)/o if pat.match(basename) host, pid = $1, $2 else next end host.gsub!(%r/^\.+|\.+$/,'') quad = host.split %r/\./ host = quad.first pat = %r/^\s*#{ host }/i if pat.match(HOSTNAME) and %r/^\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
synchronize() { || ... }
click to toggle source
Executes the given block after acquiring the lock and ensures that the lock is relinquished afterwards.
# File lib/lockfile.rb, line 202 def synchronize raise ArgumentError, 'block must be given' unless block_given? begin lock yield ensure unlock end end
timestamp()
click to toggle source
# File lib/lockfile.rb, line 464 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 488 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_str()
click to toggle source
# File lib/lockfile.rb, line 520 def to_str @path end
Also aliased as: to_s
touch(path)
click to toggle source
# File lib/lockfile.rb, line 509 def touch(path) FileUtils.touch path end
trace(s = nil) { || ... }
click to toggle source
# File lib/lockfile.rb, line 525 def trace(s = nil) STDERR.puts((s ? s : yield)) if @debug end
try_again!()
click to toggle source
# File lib/lockfile.rb, line 539 def try_again! throw 'attempt', 'try_again' end
Also aliased as: again!
uncache(file)
click to toggle source
# File lib/lockfile.rb, line 419 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 367 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 405 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