In Files

Parent

Lockfile

Constants

VERSION
HOSTNAME
DEFAULT_RETRIES
DEFAULT_TIMEOUT
DEFAULT_MAX_AGE
DEFAULT_SLEEP_INC
DEFAULT_MIN_SLEEP
DEFAULT_MAX_SLEEP
DEFAULT_SUSPEND
DEFAULT_REFRESH
DEFAULT_DONT_CLEAN
DEFAULT_POLL_RETRIES
DEFAULT_POLL_MAX_SLEEP
DEFAULT_DONT_SWEEP
DEFAULT_DONT_USE_LOCK_ID
DEFAULT_DEBUG

Attributes

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

Public Class Methods

create(path, *a, &b) click to toggle source
     # 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
init() click to toggle source
     # 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
new(path, opts = {}, &block) click to toggle source
     # 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
version() click to toggle source
    # File lib/lockfile.rb, line 10
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
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
attempt() click to toggle source
     # 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
create(path) click to toggle source
     # 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
create_tmplock() click to toggle source
     # 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
dump_lock_id(lock_id = @lock_id) click to toggle source
     # 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
errmsg(e) click to toggle source
     # 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
gen_lock_id() click to toggle source
     # 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
getopt(key, default = nil) click to toggle source
     # 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
give_up!() click to toggle source
     # File lib/lockfile.rb, line 526
526:     def give_up!
527:       throw 'attempt', 'give_up'
528:     end
load_lock_id(buf) click to toggle source
     # 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
lock() click to toggle source
     # 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
new_refresher() click to toggle source
     # 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
sweep() click to toggle source
     # 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
timestamp() click to toggle source
     # 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
tmpnam(dir, seed = File.basename($0)) click to toggle source
     # 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
to_s() click to toggle source
Alias for: to_str
to_str() click to toggle source
     # File lib/lockfile.rb, line 502
502:     def to_str
503:       @path
504:     end
Also aliased as: to_s
touch(path) click to toggle source
     # File lib/lockfile.rb, line 491
491:     def touch(path)
492:       FileUtils.touch path
493:     end
trace(s = nil) click to toggle source
     # File lib/lockfile.rb, line 507
507:     def trace(s = nil)
508:       STDERR.puts((s ? s : yield)) if @debug
509:     end
try_again!() click to toggle source
     # File lib/lockfile.rb, line 521
521:     def try_again!
522:       throw 'attempt', 'try_again'
523:     end
Also aliased as: again!
uncache(file) click to toggle source
     # 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
unlock() click to toggle source
     # 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
validlock?() click to toggle source
     # 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
version() click to toggle source
    # File lib/lockfile.rb, line 11
11:     def version() Lockfile::VERSION end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.