# File lib/lockfile.rb, line 139 139: def Lockfile.create(path, *a, &b) 140: opts = { 141: 'retries' => 0, 142: 'min_sleep' => 0, 143: 'max_sleep' => 1, 144: 'sleep_inc' => 1, 145: 'max_age' => nil, 146: 'suspend' => 0, 147: 'refresh' => nil, 148: 'timeout' => nil, 149: 'poll_retries' => 0, 150: 'dont_clean' => true, 151: 'dont_sweep' => false, 152: 'dont_use_lock_id' => true, 153: } 154: begin 155: new(path, opts).lock 156: rescue LockError 157: raise Errno::EEXIST, path 158: end 159: open(path, *a, &b) 160: end
# File lib/lockfile.rb, line 86 86: def init 87: @retries = DEFAULT_RETRIES 88: @max_age = DEFAULT_MAX_AGE 89: @sleep_inc = DEFAULT_SLEEP_INC 90: @min_sleep = DEFAULT_MIN_SLEEP 91: @max_sleep = DEFAULT_MAX_SLEEP 92: @suspend = DEFAULT_SUSPEND 93: @timeout = DEFAULT_TIMEOUT 94: @refresh = DEFAULT_REFRESH 95: @dont_clean = DEFAULT_DONT_CLEAN 96: @poll_retries = DEFAULT_POLL_RETRIES 97: @poll_max_sleep = DEFAULT_POLL_MAX_SLEEP 98: @dont_sweep = DEFAULT_DONT_SWEEP 99: @dont_use_lock_id = DEFAULT_DONT_USE_LOCK_ID 100: 101: @debug = DEFAULT_DEBUG 102: 103: STDOUT.sync = true if @debug 104: STDERR.sync = true if @debug 105: end
# File lib/lockfile.rb, line 162 162: def initialize(path, opts = {}, &block) 163: @klass = self.class 164: @path = path 165: @opts = opts 166: 167: @retries = getopt 'retries' , @klass.retries 168: @max_age = getopt 'max_age' , @klass.max_age 169: @sleep_inc = getopt 'sleep_inc' , @klass.sleep_inc 170: @min_sleep = getopt 'min_sleep' , @klass.min_sleep 171: @max_sleep = getopt 'max_sleep' , @klass.max_sleep 172: @suspend = getopt 'suspend' , @klass.suspend 173: @timeout = getopt 'timeout' , @klass.timeout 174: @refresh = getopt 'refresh' , @klass.refresh 175: @dont_clean = getopt 'dont_clean' , @klass.dont_clean 176: @poll_retries = getopt 'poll_retries' , @klass.poll_retries 177: @poll_max_sleep = getopt 'poll_max_sleep' , @klass.poll_max_sleep 178: @dont_sweep = getopt 'dont_sweep' , @klass.dont_sweep 179: @dont_use_lock_id = getopt 'dont_use_lock_id' , @klass.dont_use_lock_id 180: @debug = getopt 'debug' , @klass.debug 181: 182: @sleep_cycle = SleepCycle.new @min_sleep, @max_sleep, @sleep_inc 183: 184: @clean = @dont_clean ? nil : lambda{ File.unlink @path rescue nil } 185: @dirname = File.dirname @path 186: @basename = File.basename @path 187: @thief = false 188: @locked = false 189: @refrsher = nil 190: 191: lock(&block) if block 192: end
# File lib/lockfile.rb, line 339 339: def alive? pid 340: pid = Integer("#{ pid }") 341: begin 342: Process.kill 0, pid 343: true 344: rescue Errno::ESRCH 345: false 346: end 347: end
# File lib/lockfile.rb, line 515 515: def attempt 516: ret = nil 517: loop{ break unless catch('attempt'){ ret = yield } == 'try_again' } 518: ret 519: end
# File lib/lockfile.rb, line 479 479: def create(path) 480: umask = nil 481: f = nil 482: begin 483: umask = File.umask 022 484: f = open path, File::WRONLY|File::CREAT|File::EXCL, 0644 485: ensure 486: File.umask umask if umask 487: end 488: return(block_given? ? begin; yield f; ensure; f.close; end : f) 489: end
# File lib/lockfile.rb, line 419 419: def create_tmplock 420: tmplock = tmpnam @dirname 421: begin 422: create(tmplock) do |f| 423: unless dont_use_lock_id 424: @lock_id = gen_lock_id 425: dumped = dump_lock_id 426: trace{"lock_id <\n#{ @lock_id.inspect }\n>"} 427: f.write dumped 428: f.flush 429: end 430: yield f 431: end 432: ensure 433: begin; File.unlink tmplock; rescue Errno::ENOENT; end if tmplock 434: end 435: end
# File lib/lockfile.rb, line 453 453: def dump_lock_id(lock_id = @lock_id) 454: "host: %s\npid: %s\nppid: %s\ntime: %s\n" % 455: lock_id.values_at('host','pid','ppid','time') 456: end
# File lib/lockfile.rb, line 511 511: def errmsg(e) 512: "%s (%s)\n%s\n" % [e.class, e.message, e.backtrace.join("\n")] 513: end
# File lib/lockfile.rb, line 437 437: def gen_lock_id 438: Hash[ 439: 'host' => "#{ HOSTNAME }", 440: 'pid' => "#{ Process.pid }", 441: 'ppid' => "#{ Process.ppid }", 442: 'time' => timestamp, 443: ] 444: end
# File lib/lockfile.rb, line 495 495: def getopt(key, default = nil) 496: [ key, key.to_s, key.to_s.intern ].each do |k| 497: return @opts[k] if @opts.has_key?(k) 498: end 499: return default 500: end
# File lib/lockfile.rb, line 526 526: def give_up! 527: throw 'attempt', 'give_up' 528: end
# File lib/lockfile.rb, line 458 458: def load_lock_id(buf) 459: lock_id = {} 460: kv = /([^:]+):(.*)/ 461: buf.each_line do |line| 462: m = kv.match line 463: k, v = m[1], m[2] 464: next unless m and k and v 465: lock_id[k.strip] = v.strip 466: end 467: lock_id 468: end
# File lib/lockfile.rb, line 194 194: def lock 195: raise StackingLockError, "<#{ @path }> is locked!" if @locked 196: 197: sweep unless @dont_sweep 198: 199: ret = nil 200: 201: attempt do 202: begin 203: @sleep_cycle.reset 204: create_tmplock do |f| 205: begin 206: Timeout.timeout(@timeout) do 207: tmp_path = f.path 208: tmp_stat = f.lstat 209: n_retries = 0 210: trace{ "attempting to lock <#{ @path }>..." } 211: begin 212: i = 0 213: begin 214: trace{ "polling attempt <#{ i }>..." } 215: begin 216: File.link tmp_path, @path 217: rescue Errno::ENOENT 218: try_again! 219: end 220: lock_stat = File.lstat @path 221: raise StatLockError, "stat's do not agree" unless 222: tmp_stat.rdev == lock_stat.rdev and tmp_stat.ino == lock_stat.ino 223: trace{ "aquired lock <#{ @path }>" } 224: @locked = true 225: rescue => e 226: i += 1 227: unless i >= @poll_retries 228: t = [rand(@poll_max_sleep), @poll_max_sleep].min 229: trace{ "poll sleep <#{ t }>..." } 230: sleep t 231: retry 232: end 233: raise 234: end 235: 236: rescue => e 237: n_retries += 1 238: trace{ "n_retries <#{ n_retries }>" } 239: case validlock? 240: when true 241: raise MaxTriesLockError, "surpased retries <#{ @retries }>" if 242: @retries and n_retries >= @retries 243: trace{ "found valid lock" } 244: sleeptime = @sleep_cycle.next 245: trace{ "sleep <#{ sleeptime }>..." } 246: sleep sleeptime 247: when false 248: trace{ "found invalid lock and removing" } 249: begin 250: File.unlink @path 251: @thief = true 252: warn "<#{ @path }> stolen by <#{ Process.pid }> at <#{ timestamp }>" 253: trace{ "i am a thief!" } 254: rescue Errno::ENOENT 255: end 256: trace{ "suspending <#{ @suspend }>" } 257: sleep @suspend 258: when nil 259: raise MaxTriesLockError, "surpased retries <#{ @retries }>" if 260: @retries and n_retries >= @retries 261: end 262: retry 263: end # begin 264: end # timeout 265: rescue Timeout::Error 266: raise TimeoutLockError, "surpassed timeout <#{ @timeout }>" 267: end # begin 268: end # create_tmplock 269: 270: if block_given? 271: stolen = false 272: @refresher = (@refresh ? new_refresher : nil) 273: begin 274: begin 275: ret = yield @path 276: rescue StolenLockError 277: stolen = true 278: raise 279: end 280: ensure 281: begin 282: @refresher.kill if @refresher and @refresher.status 283: @refresher = nil 284: ensure 285: unlock unless stolen 286: end 287: end 288: else 289: @refresher = (@refresh ? new_refresher : nil) 290: ObjectSpace.define_finalizer self, @clean if @clean 291: ret = self 292: end 293: rescue Errno::ESTALE, Errno::EIO => e 294: raise(NFSLockError, errmsg(e)) 295: end 296: end 297: 298: return ret 299: end
# File lib/lockfile.rb, line 366 366: def new_refresher 367: Thread.new(Thread.current, @path, @refresh, @dont_use_lock_id) do |thread, path, refresh, dont_use_lock_id| 368: loop do 369: begin 370: touch path 371: trace{"touched <#{ path }> @ <#{ Time.now.to_f }>"} 372: unless dont_use_lock_id 373: loaded = load_lock_id(IO.read(path)) 374: trace{"loaded <\n#{ loaded.inspect }\n>"} 375: raise unless loaded == @lock_id 376: end 377: sleep refresh 378: rescue Exception => e 379: trace{errmsg e} 380: thread.raise StolenLockError 381: Thread.exit 382: end 383: end 384: end 385: end
# File lib/lockfile.rb, line 301 301: def sweep 302: begin 303: glob = File.join(@dirname, ".*lck") 304: paths = Dir[glob] 305: paths.each do |path| 306: begin 307: basename = File.basename path 308: pat = /^\s*\.([^_]+)_([^_]+)/ 309: if pat.match(basename) 310: host, pid = $1, $2 311: else 312: next 313: end 314: host.gsub!(/^\.+|\.+$/,'') 315: quad = host.split /\./ 316: host = quad.first 317: pat = /^\s*#{ host }/ 318: if pat.match(HOSTNAME) and /^\s*\d+\s*$/.match(pid) 319: unless alive?(pid) 320: trace{ "process <#{ pid }> on <#{ host }> is no longer alive" } 321: trace{ "sweeping <#{ path }>" } 322: FileUtils.rm_f path 323: else 324: trace{ "process <#{ pid }> on <#{ host }> is still alive" } 325: trace{ "ignoring <#{ path }>" } 326: end 327: else 328: trace{ "ignoring <#{ path }> generated by <#{ host }>" } 329: end 330: rescue 331: next 332: end 333: end 334: rescue => e 335: warn(errmsg(e)) 336: end 337: end
# File lib/lockfile.rb, line 446 446: def timestamp 447: time = Time.now 448: usec = time.usec.to_s 449: usec << '0' while usec.size < 6 450: "#{ time.strftime('%Y-%m-%d %H:%M:%S') }.#{ usec }" 451: end
# File lib/lockfile.rb, line 470 470: def tmpnam(dir, seed = File.basename($0)) 471: pid = Process.pid 472: time = Time.now 473: sec = time.to_i 474: usec = time.usec 475: "%s%s.%s_%d_%s_%d_%d_%d.lck" % 476: [dir, File::SEPARATOR, HOSTNAME, pid, seed, sec, usec, rand(sec)] 477: end
# File lib/lockfile.rb, line 502 502: def to_str 503: @path 504: end
# File lib/lockfile.rb, line 491 491: def touch(path) 492: FileUtils.touch path 493: end
# File lib/lockfile.rb, line 507 507: def trace(s = nil) 508: STDERR.puts((s ? s : yield)) if @debug 509: end
# File lib/lockfile.rb, line 521 521: def try_again! 522: throw 'attempt', 'try_again' 523: end
# File lib/lockfile.rb, line 401 401: def uncache file 402: refresh = nil 403: begin 404: is_a_file = File === file 405: path = (is_a_file ? file.path : file.to_s) 406: stat = (is_a_file ? file.stat : File.stat(file.to_s)) 407: refresh = tmpnam(File.dirname(path)) 408: File.link path, refresh 409: File.chmod stat.mode, path 410: File.utime stat.atime, stat.mtime, path 411: ensure 412: begin 413: File.unlink refresh if refresh 414: rescue Errno::ENOENT 415: end 416: end 417: end
# File lib/lockfile.rb, line 349 349: def unlock 350: raise UnLockError, "<#{ @path }> is not locked!" unless @locked 351: 352: @refresher.kill if @refresher and @refresher.status 353: @refresher = nil 354: 355: begin 356: File.unlink @path 357: rescue Errno::ENOENT 358: raise StolenLockError, @path 359: ensure 360: @thief = false 361: @locked = false 362: ObjectSpace.undefine_finalizer self if @clean 363: end 364: end
# File lib/lockfile.rb, line 387 387: def validlock? 388: if @max_age 389: uncache @path rescue nil 390: begin 391: return((Time.now - File.stat(@path).mtime) < @max_age) 392: rescue Errno::ENOENT 393: return nil 394: end 395: else 396: exist = File.exist?(@path) 397: return(exist ? true : nil) 398: end 399: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.