scout-essentials 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.vimproject +78 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/scout/cmd.rb +348 -0
- data/lib/scout/concurrent_stream.rb +284 -0
- data/lib/scout/config.rb +168 -0
- data/lib/scout/exceptions.rb +77 -0
- data/lib/scout/indiferent_hash/case_insensitive.rb +30 -0
- data/lib/scout/indiferent_hash/options.rb +115 -0
- data/lib/scout/indiferent_hash.rb +96 -0
- data/lib/scout/log/color.rb +224 -0
- data/lib/scout/log/color_class.rb +269 -0
- data/lib/scout/log/fingerprint.rb +69 -0
- data/lib/scout/log/progress/report.rb +244 -0
- data/lib/scout/log/progress/util.rb +173 -0
- data/lib/scout/log/progress.rb +106 -0
- data/lib/scout/log/trap.rb +107 -0
- data/lib/scout/log.rb +441 -0
- data/lib/scout/meta_extension.rb +100 -0
- data/lib/scout/misc/digest.rb +63 -0
- data/lib/scout/misc/filesystem.rb +25 -0
- data/lib/scout/misc/format.rb +255 -0
- data/lib/scout/misc/helper.rb +31 -0
- data/lib/scout/misc/insist.rb +56 -0
- data/lib/scout/misc/monitor.rb +66 -0
- data/lib/scout/misc/system.rb +73 -0
- data/lib/scout/misc.rb +10 -0
- data/lib/scout/named_array.rb +138 -0
- data/lib/scout/open/lock/lockfile.rb +587 -0
- data/lib/scout/open/lock.rb +68 -0
- data/lib/scout/open/remote.rb +135 -0
- data/lib/scout/open/stream.rb +491 -0
- data/lib/scout/open/util.rb +244 -0
- data/lib/scout/open.rb +170 -0
- data/lib/scout/path/find.rb +204 -0
- data/lib/scout/path/tmpfile.rb +8 -0
- data/lib/scout/path/util.rb +127 -0
- data/lib/scout/path.rb +51 -0
- data/lib/scout/persist/open.rb +17 -0
- data/lib/scout/persist/path.rb +15 -0
- data/lib/scout/persist/serialize.rb +157 -0
- data/lib/scout/persist.rb +104 -0
- data/lib/scout/resource/open.rb +8 -0
- data/lib/scout/resource/path.rb +80 -0
- data/lib/scout/resource/produce/rake.rb +69 -0
- data/lib/scout/resource/produce.rb +151 -0
- data/lib/scout/resource/scout.rb +3 -0
- data/lib/scout/resource/software.rb +178 -0
- data/lib/scout/resource/util.rb +59 -0
- data/lib/scout/resource.rb +40 -0
- data/lib/scout/simple_opt/accessor.rb +54 -0
- data/lib/scout/simple_opt/doc.rb +126 -0
- data/lib/scout/simple_opt/get.rb +57 -0
- data/lib/scout/simple_opt/parse.rb +67 -0
- data/lib/scout/simple_opt/setup.rb +26 -0
- data/lib/scout/simple_opt.rb +5 -0
- data/lib/scout/tmpfile.rb +129 -0
- data/lib/scout-essentials.rb +10 -0
- data/scout-essentials.gemspec +143 -0
- data/share/color/color_names +507 -0
- data/share/color/diverging_colors.hex +12 -0
- data/share/software/install_helpers +523 -0
- data/test/scout/indiferent_hash/test_case_insensitive.rb +16 -0
- data/test/scout/indiferent_hash/test_options.rb +46 -0
- data/test/scout/log/test_color.rb +0 -0
- data/test/scout/log/test_progress.rb +108 -0
- data/test/scout/misc/test_digest.rb +30 -0
- data/test/scout/misc/test_filesystem.rb +30 -0
- data/test/scout/misc/test_insist.rb +13 -0
- data/test/scout/misc/test_system.rb +21 -0
- data/test/scout/open/test_lock.rb +52 -0
- data/test/scout/open/test_remote.rb +25 -0
- data/test/scout/open/test_stream.rb +676 -0
- data/test/scout/open/test_util.rb +73 -0
- data/test/scout/path/test_find.rb +110 -0
- data/test/scout/path/test_util.rb +22 -0
- data/test/scout/persist/test_open.rb +37 -0
- data/test/scout/persist/test_path.rb +37 -0
- data/test/scout/persist/test_serialize.rb +114 -0
- data/test/scout/resource/test_path.rb +58 -0
- data/test/scout/resource/test_produce.rb +94 -0
- data/test/scout/resource/test_software.rb +24 -0
- data/test/scout/resource/test_util.rb +38 -0
- data/test/scout/simple_opt/test_doc.rb +16 -0
- data/test/scout/simple_opt/test_get.rb +11 -0
- data/test/scout/simple_opt/test_parse.rb +10 -0
- data/test/scout/simple_opt/test_setup.rb +77 -0
- data/test/scout/test_cmd.rb +85 -0
- data/test/scout/test_concurrent_stream.rb +29 -0
- data/test/scout/test_config.rb +66 -0
- data/test/scout/test_indiferent_hash.rb +26 -0
- data/test/scout/test_log.rb +32 -0
- data/test/scout/test_meta_extension.rb +80 -0
- data/test/scout/test_misc.rb +6 -0
- data/test/scout/test_named_array.rb +43 -0
- data/test/scout/test_open.rb +146 -0
- data/test/scout/test_path.rb +54 -0
- data/test/scout/test_persist.rb +186 -0
- data/test/scout/test_resource.rb +26 -0
- data/test/scout/test_tmpfile.rb +53 -0
- data/test/test_helper.rb +50 -0
- 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
|