scout-essentials 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.vimproject +78 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +18 -0
  7. data/Rakefile +47 -0
  8. data/VERSION +1 -0
  9. data/lib/scout/cmd.rb +348 -0
  10. data/lib/scout/concurrent_stream.rb +284 -0
  11. data/lib/scout/config.rb +168 -0
  12. data/lib/scout/exceptions.rb +77 -0
  13. data/lib/scout/indiferent_hash/case_insensitive.rb +30 -0
  14. data/lib/scout/indiferent_hash/options.rb +115 -0
  15. data/lib/scout/indiferent_hash.rb +96 -0
  16. data/lib/scout/log/color.rb +224 -0
  17. data/lib/scout/log/color_class.rb +269 -0
  18. data/lib/scout/log/fingerprint.rb +69 -0
  19. data/lib/scout/log/progress/report.rb +244 -0
  20. data/lib/scout/log/progress/util.rb +173 -0
  21. data/lib/scout/log/progress.rb +106 -0
  22. data/lib/scout/log/trap.rb +107 -0
  23. data/lib/scout/log.rb +441 -0
  24. data/lib/scout/meta_extension.rb +100 -0
  25. data/lib/scout/misc/digest.rb +63 -0
  26. data/lib/scout/misc/filesystem.rb +25 -0
  27. data/lib/scout/misc/format.rb +255 -0
  28. data/lib/scout/misc/helper.rb +31 -0
  29. data/lib/scout/misc/insist.rb +56 -0
  30. data/lib/scout/misc/monitor.rb +66 -0
  31. data/lib/scout/misc/system.rb +73 -0
  32. data/lib/scout/misc.rb +10 -0
  33. data/lib/scout/named_array.rb +138 -0
  34. data/lib/scout/open/lock/lockfile.rb +587 -0
  35. data/lib/scout/open/lock.rb +68 -0
  36. data/lib/scout/open/remote.rb +135 -0
  37. data/lib/scout/open/stream.rb +491 -0
  38. data/lib/scout/open/util.rb +244 -0
  39. data/lib/scout/open.rb +170 -0
  40. data/lib/scout/path/find.rb +204 -0
  41. data/lib/scout/path/tmpfile.rb +8 -0
  42. data/lib/scout/path/util.rb +127 -0
  43. data/lib/scout/path.rb +51 -0
  44. data/lib/scout/persist/open.rb +17 -0
  45. data/lib/scout/persist/path.rb +15 -0
  46. data/lib/scout/persist/serialize.rb +157 -0
  47. data/lib/scout/persist.rb +104 -0
  48. data/lib/scout/resource/open.rb +8 -0
  49. data/lib/scout/resource/path.rb +80 -0
  50. data/lib/scout/resource/produce/rake.rb +69 -0
  51. data/lib/scout/resource/produce.rb +151 -0
  52. data/lib/scout/resource/scout.rb +3 -0
  53. data/lib/scout/resource/software.rb +178 -0
  54. data/lib/scout/resource/util.rb +59 -0
  55. data/lib/scout/resource.rb +40 -0
  56. data/lib/scout/simple_opt/accessor.rb +54 -0
  57. data/lib/scout/simple_opt/doc.rb +126 -0
  58. data/lib/scout/simple_opt/get.rb +57 -0
  59. data/lib/scout/simple_opt/parse.rb +67 -0
  60. data/lib/scout/simple_opt/setup.rb +26 -0
  61. data/lib/scout/simple_opt.rb +5 -0
  62. data/lib/scout/tmpfile.rb +129 -0
  63. data/lib/scout-essentials.rb +10 -0
  64. data/scout-essentials.gemspec +143 -0
  65. data/share/color/color_names +507 -0
  66. data/share/color/diverging_colors.hex +12 -0
  67. data/share/software/install_helpers +523 -0
  68. data/test/scout/indiferent_hash/test_case_insensitive.rb +16 -0
  69. data/test/scout/indiferent_hash/test_options.rb +46 -0
  70. data/test/scout/log/test_color.rb +0 -0
  71. data/test/scout/log/test_progress.rb +108 -0
  72. data/test/scout/misc/test_digest.rb +30 -0
  73. data/test/scout/misc/test_filesystem.rb +30 -0
  74. data/test/scout/misc/test_insist.rb +13 -0
  75. data/test/scout/misc/test_system.rb +21 -0
  76. data/test/scout/open/test_lock.rb +52 -0
  77. data/test/scout/open/test_remote.rb +25 -0
  78. data/test/scout/open/test_stream.rb +676 -0
  79. data/test/scout/open/test_util.rb +73 -0
  80. data/test/scout/path/test_find.rb +110 -0
  81. data/test/scout/path/test_util.rb +22 -0
  82. data/test/scout/persist/test_open.rb +37 -0
  83. data/test/scout/persist/test_path.rb +37 -0
  84. data/test/scout/persist/test_serialize.rb +114 -0
  85. data/test/scout/resource/test_path.rb +58 -0
  86. data/test/scout/resource/test_produce.rb +94 -0
  87. data/test/scout/resource/test_software.rb +24 -0
  88. data/test/scout/resource/test_util.rb +38 -0
  89. data/test/scout/simple_opt/test_doc.rb +16 -0
  90. data/test/scout/simple_opt/test_get.rb +11 -0
  91. data/test/scout/simple_opt/test_parse.rb +10 -0
  92. data/test/scout/simple_opt/test_setup.rb +77 -0
  93. data/test/scout/test_cmd.rb +85 -0
  94. data/test/scout/test_concurrent_stream.rb +29 -0
  95. data/test/scout/test_config.rb +66 -0
  96. data/test/scout/test_indiferent_hash.rb +26 -0
  97. data/test/scout/test_log.rb +32 -0
  98. data/test/scout/test_meta_extension.rb +80 -0
  99. data/test/scout/test_misc.rb +6 -0
  100. data/test/scout/test_named_array.rb +43 -0
  101. data/test/scout/test_open.rb +146 -0
  102. data/test/scout/test_path.rb +54 -0
  103. data/test/scout/test_persist.rb +186 -0
  104. data/test/scout/test_resource.rb +26 -0
  105. data/test/scout/test_tmpfile.rb +53 -0
  106. data/test/test_helper.rb +50 -0
  107. metadata +247 -0
@@ -0,0 +1,587 @@
1
+ # Originaly from here https://github.com/ahoward/lockfile
2
+ # Author: Ara T. Howard (Ara T. Howard)
3
+ # Licence: Ruby (as listed [here](https://github.com/ahoward/lockfile/blob/20ab06f29bda69b9773d799510d00585b6a27e3b/lockfile.gemspec#L10))
4
+ # with modifications for scout-gear and rbbt by Miguel Vazquez
5
+ unless(defined?($__lockfile__) or defined?(Lockfile))
6
+
7
+ require 'socket'
8
+ require 'timeout'
9
+ require 'fileutils'
10
+
11
+ class Lockfile
12
+
13
+ VERSION = '2.1.8'
14
+ def Lockfile.version() Lockfile::VERSION end
15
+ def version() Lockfile::VERSION end
16
+
17
+ def Lockfile.description
18
+ 'a ruby library for creating perfect and NFS safe lockfiles'
19
+ end
20
+
21
+ class LockError < StandardError; end
22
+ class StolenLockError < LockError; end
23
+ class StackingLockError < LockError; end
24
+ class StatLockError < LockError; end
25
+ class MaxTriesLockError < LockError; end
26
+ class TimeoutLockError < LockError; end
27
+ class NFSLockError < LockError; end
28
+ class UnLockError < LockError; end
29
+
30
+ class SleepCycle < Array
31
+ attr_reader :min
32
+ attr_reader :max
33
+ attr_reader :range
34
+ attr_reader :inc
35
+
36
+ def initialize(min, max, inc)
37
+ @min, @max, @inc = Float(min), Float(max), Float(inc)
38
+ @range = @max - @min
39
+ raise RangeError, "max(#{ @max }) <= min(#{ @min })" if @max <= @min
40
+ raise RangeError, "inc(#{ @inc }) > range(#{ @range })" if @inc > @range
41
+ raise RangeError, "inc(#{ @inc }) <= 0" if @inc <= 0
42
+ raise RangeError, "range(#{ @range }) <= 0" if @range <= 0
43
+ s = @min
44
+ push(s) and s += @inc while(s <= @max)
45
+ self[-1] = @max if self[-1] < @max
46
+ reset
47
+ end
48
+
49
+ def next
50
+ ret = self[@idx]
51
+ @idx = ((@idx + 1) % self.size)
52
+ ret
53
+ end
54
+
55
+ def reset
56
+ @idx = 0
57
+ end
58
+ end
59
+
60
+ HOSTNAME = Socket.gethostname
61
+
62
+ DEFAULT_RETRIES = nil # maximum number of attempts
63
+ DEFAULT_TIMEOUT = nil # the longest we will try
64
+ DEFAULT_MAX_AGE = 3600 # lockfiles older than this are stale
65
+ DEFAULT_SLEEP_INC = 2 # sleep cycle is this much longer each time
66
+ DEFAULT_MIN_SLEEP = 2 # shortest sleep time
67
+ DEFAULT_MAX_SLEEP = 32 # longest sleep time
68
+ DEFAULT_SUSPEND = 1800 # iff we steal a lock wait this long before we go on
69
+ DEFAULT_REFRESH = 8 # how often we touch/validate the lock
70
+ DEFAULT_DONT_CLEAN = false # iff we leave lock files lying around
71
+ DEFAULT_POLL_RETRIES = 16 # this many polls makes one 'try'
72
+ DEFAULT_POLL_MAX_SLEEP = 0.08 # the longest we'll sleep between polls
73
+ DEFAULT_DONT_SWEEP = false # if we cleanup after other process on our host
74
+ DEFAULT_DONT_USE_LOCK_ID = false # if we dump lock info into lockfile
75
+
76
+ DEFAULT_DEBUG = ENV['LOCKFILE_DEBUG'] || false
77
+
78
+ class << Lockfile
79
+ attr_accessor :retries
80
+ attr_accessor :max_age
81
+ attr_accessor :sleep_inc
82
+ attr_accessor :min_sleep
83
+ attr_accessor :max_sleep
84
+ attr_accessor :suspend
85
+ attr_accessor :timeout
86
+ attr_accessor :refresh
87
+ attr_accessor :debug
88
+ attr_accessor :dont_clean
89
+ attr_accessor :poll_retries
90
+ attr_accessor :poll_max_sleep
91
+ attr_accessor :dont_sweep
92
+ attr_accessor :dont_use_lock_id
93
+
94
+ def init
95
+ @retries = DEFAULT_RETRIES
96
+ @max_age = DEFAULT_MAX_AGE
97
+ @sleep_inc = DEFAULT_SLEEP_INC
98
+ @min_sleep = DEFAULT_MIN_SLEEP
99
+ @max_sleep = DEFAULT_MAX_SLEEP
100
+ @suspend = DEFAULT_SUSPEND
101
+ @timeout = DEFAULT_TIMEOUT
102
+ @refresh = DEFAULT_REFRESH
103
+ @dont_clean = DEFAULT_DONT_CLEAN
104
+ @poll_retries = DEFAULT_POLL_RETRIES
105
+ @poll_max_sleep = DEFAULT_POLL_MAX_SLEEP
106
+ @dont_sweep = DEFAULT_DONT_SWEEP
107
+ @dont_use_lock_id = DEFAULT_DONT_USE_LOCK_ID
108
+
109
+ @debug = DEFAULT_DEBUG
110
+
111
+ STDOUT.sync = true if @debug
112
+ STDERR.sync = true if @debug
113
+ end
114
+ end
115
+
116
+ Lockfile.init
117
+
118
+ attr_reader :klass
119
+ attr_reader :path
120
+ attr_reader :opts
121
+ attr_reader :locked
122
+ attr_reader :thief
123
+ attr_reader :refresher
124
+ attr_reader :dirname
125
+ attr_reader :basename
126
+ attr_reader :clean
127
+ attr_reader :retries
128
+ attr_reader :max_age
129
+ attr_reader :sleep_inc
130
+ attr_reader :min_sleep
131
+ attr_reader :max_sleep
132
+ attr_reader :suspend
133
+ attr_reader :refresh
134
+ attr_reader :timeout
135
+ attr_reader :dont_clean
136
+ attr_reader :poll_retries
137
+ attr_reader :poll_max_sleep
138
+ attr_reader :dont_sweep
139
+ attr_reader :dont_use_lock_id
140
+
141
+ attr_accessor :debug
142
+
143
+ alias thief? thief
144
+ alias locked? locked
145
+ alias debug? debug
146
+
147
+ def Lockfile.create(path, *a, &b)
148
+ opts = {
149
+ 'retries' => 0,
150
+ 'min_sleep' => 0,
151
+ 'max_sleep' => 1,
152
+ 'sleep_inc' => 1,
153
+ 'max_age' => nil,
154
+ 'suspend' => 0,
155
+ 'refresh' => nil,
156
+ 'timeout' => nil,
157
+ 'poll_retries' => 0,
158
+ 'dont_clean' => true,
159
+ 'dont_sweep' => false,
160
+ 'dont_use_lock_id' => true,
161
+ }
162
+ begin
163
+ new(path, opts).lock
164
+ rescue LockError
165
+ raise Errno::EEXIST, path
166
+ end
167
+ open(path, *a, &b)
168
+ end
169
+
170
+ def self.finalizer_proc(file)
171
+ pid = Process.pid
172
+ lambda do |id|
173
+ File.unlink file if Process.pid == pid
174
+ rescue
175
+ nil
176
+ end
177
+ end
178
+
179
+ def initialize(path, opts = {}, &block)
180
+ @klass = self.class
181
+ @path = path
182
+ @opts = opts
183
+
184
+ @retries = getopt 'retries' , @klass.retries
185
+ @max_age = getopt 'max_age' , @klass.max_age
186
+ @sleep_inc = getopt 'sleep_inc' , @klass.sleep_inc
187
+ @min_sleep = getopt 'min_sleep' , @klass.min_sleep
188
+ @max_sleep = getopt 'max_sleep' , @klass.max_sleep
189
+ @suspend = getopt 'suspend' , @klass.suspend
190
+ @timeout = getopt 'timeout' , @klass.timeout
191
+ @refresh = getopt 'refresh' , @klass.refresh
192
+ @dont_clean = getopt 'dont_clean' , @klass.dont_clean
193
+ @poll_retries = getopt 'poll_retries' , @klass.poll_retries
194
+ @poll_max_sleep = getopt 'poll_max_sleep' , @klass.poll_max_sleep
195
+ @dont_sweep = getopt 'dont_sweep' , @klass.dont_sweep
196
+ @dont_use_lock_id = getopt 'dont_use_lock_id' , @klass.dont_use_lock_id
197
+ @debug = getopt 'debug' , @klass.debug
198
+
199
+ @semaphore = Mutex.new
200
+
201
+ @sleep_cycle = SleepCycle.new @min_sleep, @max_sleep, @sleep_inc
202
+
203
+ @clean = @dont_clean ? nil : Lockfile.finalizer_proc(@path)
204
+
205
+ @dirname = File.dirname @path
206
+ @basename = File.basename @path
207
+ @thief = false
208
+ @locked = false
209
+ @refrsher = nil
210
+
211
+ lock(&block) if block
212
+ end
213
+
214
+ ##
215
+ # Executes the given block after acquiring the lock and
216
+ # ensures that the lock is relinquished afterwards.
217
+ #
218
+ def synchronize
219
+ raise ArgumentError, 'block must be given' unless block_given?
220
+ begin
221
+ lock
222
+ yield
223
+ ensure
224
+ unlock
225
+ end
226
+ end
227
+
228
+ def lock
229
+ raise StackingLockError, "<#{ @path }> is locked!" if @locked
230
+
231
+ sweep unless @dont_sweep
232
+
233
+ ret = nil
234
+
235
+ attempt do
236
+ begin
237
+ @sleep_cycle.reset
238
+ create_tmplock do |f|
239
+ begin
240
+ Timeout.timeout(@timeout) do
241
+ tmp_path = f.path
242
+ tmp_stat = f.lstat
243
+ n_retries = 0
244
+ trace{ "attempting to lock <#{ @path }>..." }
245
+ begin
246
+ i = 0
247
+ begin
248
+ trace{ "polling attempt <#{ i }>..." }
249
+ begin
250
+ File.link tmp_path, @path
251
+ rescue Errno::ENOENT
252
+ try_again!
253
+ end
254
+ lock_stat = File.lstat @path
255
+ raise StatLockError, "stat's do not agree" unless
256
+ tmp_stat.rdev == lock_stat.rdev and tmp_stat.ino == lock_stat.ino
257
+ trace{ "aquired lock <#{ @path }>" }
258
+ @locked = true
259
+ rescue => e
260
+ i += 1
261
+ unless i >= @poll_retries
262
+ t = [rand(@poll_max_sleep), @poll_max_sleep].min
263
+ trace{ "poll sleep <#{ t }>..." }
264
+ sleep t
265
+ retry
266
+ end
267
+ raise
268
+ end
269
+
270
+ rescue => e
271
+ n_retries += 1
272
+ trace{ "n_retries <#{ n_retries }>" }
273
+ case validlock?
274
+ when true
275
+ raise MaxTriesLockError, "surpased retries <#{ @retries }>" if
276
+ @retries and n_retries >= @retries
277
+ trace{ "found valid lock" }
278
+ sleeptime = @sleep_cycle.next
279
+ trace{ "sleep <#{ sleeptime }>..." }
280
+ sleep sleeptime
281
+ when false
282
+ trace{ "found invalid lock and removing" }
283
+ begin
284
+ File.unlink @path
285
+ @thief = true
286
+ warn "<#{ @path }> stolen by <#{ Process.pid }> at <#{ timestamp }>"
287
+ trace{ "i am a thief!" }
288
+ rescue Errno::ENOENT
289
+ end
290
+ trace{ "suspending <#{ @suspend }>" }
291
+ sleep @suspend
292
+ when nil
293
+ raise MaxTriesLockError, "surpased retries <#{ @retries }>" if
294
+ @retries and n_retries >= @retries
295
+ end
296
+ retry
297
+ end # begin
298
+ end # timeout
299
+ rescue Timeout::Error
300
+ raise TimeoutLockError, "surpassed timeout <#{ @timeout }>"
301
+ end # begin
302
+ end # create_tmplock
303
+
304
+ if block_given?
305
+ stolen = false
306
+ @refresher = (@refresh ? new_refresher : nil)
307
+ begin
308
+ begin
309
+ ret = yield @path
310
+ rescue StolenLockError
311
+ stolen = true
312
+ raise
313
+ end
314
+ ensure
315
+ begin
316
+ begin
317
+ @semaphore.synchronize do
318
+ @refresher.kill
319
+ end
320
+ rescue
321
+ @refresher.kill
322
+ end if @refresher and @refresher.status
323
+ @refresher = nil
324
+ ensure
325
+ unlock unless stolen
326
+ end
327
+ end
328
+ else
329
+ @refresher = (@refresh ? new_refresher : nil)
330
+ ObjectSpace.define_finalizer self, @clean if @clean
331
+ ret = self
332
+ end
333
+ rescue Errno::ESTALE, Errno::EIO => e
334
+ raise(NFSLockError, errmsg(e))
335
+ end
336
+ end
337
+
338
+ return ret
339
+ end
340
+
341
+ def sweep
342
+ begin
343
+ glob = File.join(@dirname, ".*lck")
344
+ paths = Dir[glob]
345
+ paths.each do |path|
346
+ begin
347
+ basename = File.basename path
348
+ pat = %r/^\s*\.([^_]+)_([^_]+)/o
349
+ if pat.match(basename)
350
+ host, pid = $1, $2
351
+ else
352
+ next
353
+ end
354
+ host.gsub!(%r/^\.+|\.+$/,'')
355
+ quad = host.split %r/\./
356
+ host = quad.first
357
+ pat = %r/^\s*#{ host }/i
358
+ if pat.match(HOSTNAME) and %r/^\s*\d+\s*$/.match(pid)
359
+ unless alive?(pid)
360
+ trace{ "process <#{ pid }> on <#{ host }> is no longer alive" }
361
+ trace{ "sweeping <#{ path }>" }
362
+ FileUtils.rm_f path
363
+ else
364
+ trace{ "process <#{ pid }> on <#{ host }> is still alive" }
365
+ trace{ "ignoring <#{ path }>" }
366
+ end
367
+ else
368
+ trace{ "ignoring <#{ path }> generated by <#{ host }>" }
369
+ end
370
+ rescue
371
+ next
372
+ end
373
+ end
374
+ rescue => e
375
+ warn(errmsg(e))
376
+ end
377
+ end
378
+
379
+ def alive? pid
380
+ pid = Integer("#{ pid }")
381
+ begin
382
+ Process.kill 0, pid
383
+ true
384
+ rescue Errno::ESRCH
385
+ false
386
+ end
387
+ end
388
+
389
+ def unlock
390
+ raise UnLockError, "<#{ @path }> is not locked!" unless @locked
391
+
392
+ begin
393
+ @semaphore.synchronize do
394
+ @refresher.kill
395
+ end
396
+ rescue
397
+ @refresher.kill
398
+ end if @refresher and @refresher.status
399
+
400
+ @refresher = nil
401
+
402
+ begin
403
+ File.unlink @path
404
+ rescue Errno::ENOENT
405
+ raise StolenLockError, @path
406
+ ensure
407
+ @thief = false
408
+ @locked = false
409
+ ObjectSpace.undefine_finalizer self if @clean
410
+ end
411
+ end
412
+
413
+ def new_refresher
414
+ Thread.new(Thread.current, @path, @refresh, @dont_use_lock_id) do |thread, path, refresh, dont_use_lock_id|
415
+ loop do
416
+ begin
417
+ touch path
418
+ trace{"touched <#{ path }> @ <#{ Time.now.to_f }>"}
419
+ unless dont_use_lock_id
420
+ txt = nil
421
+ @semaphore.synchronize do
422
+ txt = IO.read(path)
423
+ end
424
+ loaded = load_lock_id(txt)
425
+ trace{"loaded <\n#{ loaded.inspect }\n>"}
426
+ raise unless loaded == @lock_id
427
+ end
428
+ sleep refresh
429
+ rescue Exception => e
430
+ trace{errmsg e}
431
+ thread.raise StolenLockError
432
+ Thread.exit
433
+ end
434
+ end
435
+ end
436
+ end
437
+
438
+ def validlock?
439
+ if @max_age
440
+ uncache @path rescue nil
441
+ begin
442
+ return((Time.now - File.stat(@path).mtime) < @max_age)
443
+ rescue Errno::ENOENT
444
+ return nil
445
+ end
446
+ else
447
+ exist = File.exist?(@path)
448
+ return(exist ? true : nil)
449
+ end
450
+ end
451
+
452
+ def uncache file
453
+ refresh = nil
454
+ begin
455
+ is_a_file = File === file
456
+ path = (is_a_file ? file.path : file.to_s)
457
+ stat = (is_a_file ? file.stat : File.stat(file.to_s))
458
+ refresh = tmpnam(File.dirname(path))
459
+ File.link path, refresh
460
+ File.chmod stat.mode, path
461
+ File.utime stat.atime, stat.mtime, path
462
+ ensure
463
+ begin
464
+ File.unlink refresh if refresh
465
+ rescue Errno::ENOENT
466
+ end
467
+ end
468
+ end
469
+
470
+ def create_tmplock
471
+ tmplock = tmpnam @dirname
472
+ begin
473
+ create(tmplock) do |f|
474
+ unless dont_use_lock_id
475
+ @lock_id = gen_lock_id
476
+ dumped = dump_lock_id
477
+ trace{"lock_id <\n#{ @lock_id.inspect }\n>"}
478
+ f.write dumped
479
+ f.flush
480
+ end
481
+ yield f
482
+ end
483
+ ensure
484
+ begin; File.unlink tmplock; rescue Errno::ENOENT; end if tmplock
485
+ end
486
+ end
487
+
488
+ def gen_lock_id
489
+ Hash[
490
+ 'host' => "#{ HOSTNAME }",
491
+ 'pid' => "#{ Process.pid }",
492
+ 'ppid' => "#{ Process.ppid }",
493
+ 'time' => timestamp,
494
+ ]
495
+ end
496
+
497
+ def timestamp
498
+ time = Time.now
499
+ usec = time.usec.to_s
500
+ usec << '0' while usec.size < 6
501
+ "#{ time.strftime('%Y-%m-%d %H:%M:%S') }.#{ usec }"
502
+ end
503
+
504
+ def dump_lock_id(lock_id = @lock_id)
505
+ "host: %s\npid: %s\nppid: %s\ntime: %s\n" %
506
+ lock_id.values_at('host','pid','ppid','time')
507
+ end
508
+
509
+ def load_lock_id(buf)
510
+ lock_id = {}
511
+ kv = %r/([^:]+):(.*)/o
512
+ buf.each_line do |line|
513
+ m = kv.match line
514
+ k, v = m[1], m[2]
515
+ next unless m and k and v
516
+ lock_id[k.strip] = v.strip
517
+ end
518
+ lock_id
519
+ end
520
+
521
+ def tmpnam(dir, seed = File.basename($0))
522
+ pid = Process.pid
523
+ time = Time.now
524
+ sec = time.to_i
525
+ usec = time.usec
526
+ "%s%s.%s_%d_%s_%d_%d_%d.lck" %
527
+ [dir, File::SEPARATOR, HOSTNAME, pid, seed, sec, usec, rand(sec)]
528
+ end
529
+
530
+ def create(path)
531
+ umask = nil
532
+ f = nil
533
+ begin
534
+ umask = File.umask 022
535
+ f = open path, File::WRONLY|File::CREAT|File::EXCL, 0644
536
+ ensure
537
+ File.umask umask if umask
538
+ end
539
+ return(block_given? ? begin; yield f; ensure; f.close; end : f)
540
+ end
541
+
542
+ def touch(path)
543
+ FileUtils.touch path
544
+ end
545
+
546
+ def getopt(key, default = nil)
547
+ [ key, key.to_s, key.to_s.intern ].each do |k|
548
+ return @opts[k] if @opts.has_key?(k)
549
+ end
550
+ return default
551
+ end
552
+
553
+ def to_str
554
+ @path
555
+ end
556
+ alias to_s to_str
557
+
558
+ def trace(s = nil)
559
+ STDERR.puts((s ? s : yield)) if @debug
560
+ end
561
+
562
+ def errmsg(e)
563
+ "%s (%s)\n%s\n" % [e.class, e.message, e.backtrace.join("\n")]
564
+ end
565
+
566
+ def attempt
567
+ ret = nil
568
+ loop{ break unless catch('attempt'){ ret = yield } == 'try_again' }
569
+ ret
570
+ end
571
+
572
+ def try_again!
573
+ throw 'attempt', 'try_again'
574
+ end
575
+ alias again! try_again!
576
+
577
+ def give_up!
578
+ throw 'attempt', 'give_up'
579
+ end
580
+ end
581
+
582
+ def Lockfile(path, *a, &b)
583
+ Lockfile.new(path, *a, &b)
584
+ end
585
+
586
+ $__lockfile__ = __FILE__
587
+ end
@@ -0,0 +1,68 @@
1
+ require_relative '../path'
2
+ require_relative '../log'
3
+ require_relative '../exceptions'
4
+ require_relative 'lock/lockfile'
5
+
6
+ module Open
7
+ def self.init_lock
8
+ Lockfile.refresh = 2
9
+ Lockfile.max_age = 30
10
+ Lockfile.suspend = 4
11
+ end
12
+
13
+ self.init_lock
14
+
15
+ def self.lock(file, unlock = true, options = {})
16
+ unlock, options = true, unlock if Hash === unlock
17
+ return yield if file.nil? and not Lockfile === options[:lock]
18
+
19
+ if Lockfile === file
20
+ lockfile = file
21
+ else
22
+ file = file.find if Path === file
23
+ FileUtils.mkdir_p File.dirname(File.expand_path(file)) unless File.exist? File.dirname(File.expand_path(file))
24
+
25
+ case options[:lock]
26
+ when Lockfile
27
+ lockfile = options[:lock]
28
+ when FalseClass
29
+ lockfile = nil
30
+ unlock = false
31
+ when Path, String
32
+ lock_path = options[:lock].find
33
+ lockfile = Lockfile.new(lock_path, options)
34
+ else
35
+ lock_path = File.expand_path(file + '.lock')
36
+ lockfile = Lockfile.new(lock_path, options)
37
+ end
38
+ end
39
+
40
+ begin
41
+ lockfile.lock unless lockfile.nil? || lockfile.locked?
42
+ rescue Aborted, Interrupt
43
+ raise LockInterrupted
44
+ end
45
+
46
+ res = nil
47
+
48
+ begin
49
+ res = yield lockfile
50
+ rescue KeepLocked
51
+ unlock = false
52
+ res = $!.payload
53
+ ensure
54
+ if unlock
55
+ begin
56
+ if lockfile.locked?
57
+ lockfile.unlock
58
+ end
59
+ rescue Exception
60
+ Log.warn "Exception unlocking: #{lockfile.path}"
61
+ Log.exception $!
62
+ end
63
+ end
64
+ end
65
+
66
+ res
67
+ end
68
+ end