lockfile 1.1.0 → 1.3.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.
- data/bin/rlock +125 -109
- data/bin/rlock-1.3.0 +342 -0
- data/lib/lockfile-1.3.0.rb +518 -0
- data/lib/lockfile.rb +286 -205
- metadata +16 -33
- data/BUGS +0 -3
- data/README +0 -165
- data/VERSION +0 -1
- data/doc/rlock.help +0 -88
- data/install.rb +0 -143
- data/lib/lockfile-1.1.0.rb +0 -437
- data/lockfile-1.1.0.gem +0 -0
- data/lockfile.gemspec +0 -22
- data/samples/a.rb +0 -112
- data/samples/nfsstore.rb +0 -203
- data/samples/test.db +0 -0
- data/samples/test.db~ +0 -0
data/lib/lockfile-1.1.0.rb
DELETED
@@ -1,437 +0,0 @@
|
|
1
|
-
unless defined? $__lockfile__
|
2
|
-
require 'socket'
|
3
|
-
require 'timeout'
|
4
|
-
require 'fileutils'
|
5
|
-
|
6
|
-
class Lockfile
|
7
|
-
#{{{
|
8
|
-
VERSION = '1.1.0'
|
9
|
-
|
10
|
-
class LockError < StandardError; end
|
11
|
-
class StolenLockError < LockError; end
|
12
|
-
class StackingLockError < LockError; end
|
13
|
-
class StatLockError < LockError; end
|
14
|
-
class MaxTriesLockError < LockError; end
|
15
|
-
class TimeoutLockError < LockError; end
|
16
|
-
class NFSLockError < LockError; end
|
17
|
-
class UnLockError < LockError; end
|
18
|
-
|
19
|
-
class SleepCycle < Array
|
20
|
-
#{{{
|
21
|
-
attr :min
|
22
|
-
attr :max
|
23
|
-
attr :range
|
24
|
-
attr :inc
|
25
|
-
def initialize min, max, inc
|
26
|
-
#{{{
|
27
|
-
@min, @max, @inc = Float(min), Float(max), Float(inc)
|
28
|
-
@range = @max - @min
|
29
|
-
raise RangeError, "max < min" if @max < @min
|
30
|
-
raise RangeError, "inc > range" if @inc > @range
|
31
|
-
s = @min
|
32
|
-
push(s) and s += @inc while(s <= @max)
|
33
|
-
self[-1] = @max if self[-1] < @max
|
34
|
-
reset
|
35
|
-
#}}}
|
36
|
-
end
|
37
|
-
def next
|
38
|
-
#{{{
|
39
|
-
ret = self[@idx]
|
40
|
-
@idx = ((@idx + 1) % self.size)
|
41
|
-
ret
|
42
|
-
#}}}
|
43
|
-
end
|
44
|
-
def reset
|
45
|
-
#{{{
|
46
|
-
@idx = 0
|
47
|
-
#}}}
|
48
|
-
end
|
49
|
-
#}}}
|
50
|
-
end
|
51
|
-
|
52
|
-
HOSTNAME = Socket::gethostname
|
53
|
-
|
54
|
-
RETRIES = nil # maximum number of attempts
|
55
|
-
TIMEOUT = nil # the longest we will try
|
56
|
-
MAX_AGE = 1024 # lockfiles older than this are stale
|
57
|
-
SLEEP_INC = 2 # sleep cycle is this much longer each time
|
58
|
-
MIN_SLEEP = 2 # shortest sleep time
|
59
|
-
MAX_SLEEP = 32 # longest sleep time
|
60
|
-
SUSPEND = 64 # iff we steal a lock wait this long before we go on
|
61
|
-
REFRESH = 8 # how often we touch/validate the lock
|
62
|
-
DONT_CLEAN = false # iff we leave lock files lying around
|
63
|
-
POLL_RETRIES = 16 # this many polls makes one 'try'
|
64
|
-
POLL_MAX_SLEEP = 0.08 # the longest we'll sleep between polls
|
65
|
-
|
66
|
-
DEBUG = ENV['LOCKFILE_DEBUG'] || false
|
67
|
-
|
68
|
-
class << self
|
69
|
-
#{{{
|
70
|
-
attr :retries, true
|
71
|
-
attr :max_age, true
|
72
|
-
attr :sleep_inc, true
|
73
|
-
attr :min_sleep, true
|
74
|
-
attr :max_sleep, true
|
75
|
-
attr :suspend, true
|
76
|
-
attr :timeout, true
|
77
|
-
attr :refresh, true
|
78
|
-
attr :debug, true
|
79
|
-
attr :dont_clean, true
|
80
|
-
attr :poll_retries, true
|
81
|
-
attr :poll_max_sleep, true
|
82
|
-
def init
|
83
|
-
#{{{
|
84
|
-
@retries = RETRIES
|
85
|
-
@max_age = MAX_AGE
|
86
|
-
@sleep_inc = SLEEP_INC
|
87
|
-
@min_sleep = MIN_SLEEP
|
88
|
-
@max_sleep = MAX_SLEEP
|
89
|
-
@suspend = SUSPEND
|
90
|
-
@timeout = TIMEOUT
|
91
|
-
@refresh = REFRESH
|
92
|
-
@debug = DEBUG
|
93
|
-
@dont_clean = DONT_CLEAN
|
94
|
-
@poll_retries = POLL_RETRIES
|
95
|
-
@poll_max_sleep = POLL_MAX_SLEEP
|
96
|
-
|
97
|
-
STDOUT.sync = true if @debug
|
98
|
-
STDERR.sync = true if @debug
|
99
|
-
#}}}
|
100
|
-
end
|
101
|
-
#}}}
|
102
|
-
end
|
103
|
-
self.init
|
104
|
-
|
105
|
-
attr :klass
|
106
|
-
attr :path
|
107
|
-
attr :opts
|
108
|
-
attr :locked
|
109
|
-
attr :thief
|
110
|
-
attr :dirname
|
111
|
-
attr :basename
|
112
|
-
attr :clean
|
113
|
-
attr :retries, true
|
114
|
-
attr :max_age, true
|
115
|
-
attr :sleep_inc, true
|
116
|
-
attr :min_sleep, true
|
117
|
-
attr :max_sleep, true
|
118
|
-
attr :suspend, true
|
119
|
-
attr :refresh, true
|
120
|
-
attr :timeout, true
|
121
|
-
attr :debug, true
|
122
|
-
attr :dont_clean, true
|
123
|
-
attr :poll_retries, true
|
124
|
-
attr :poll_max_sleep, true
|
125
|
-
|
126
|
-
alias thief? thief
|
127
|
-
alias locked? locked
|
128
|
-
alias debug? debug
|
129
|
-
|
130
|
-
def initialize(path, opts = {}, &block)
|
131
|
-
#{{{
|
132
|
-
@klass = self.class
|
133
|
-
@path = path
|
134
|
-
@opts = opts
|
135
|
-
|
136
|
-
@retries = getopt('retries') || @klass.retries
|
137
|
-
@max_age = getopt('max_age') || @klass.max_age
|
138
|
-
@sleep_inc = getopt('sleep_inc') || @klass.sleep_inc
|
139
|
-
@min_sleep = getopt('min_sleep') || @klass.min_sleep
|
140
|
-
@max_sleep = getopt('max_sleep') || @klass.max_sleep
|
141
|
-
@suspend = getopt('suspend') || @klass.suspend
|
142
|
-
@timeout = getopt('timeout') || @klass.timeout
|
143
|
-
@refresh = getopt('refresh') || @klass.refresh
|
144
|
-
@debug = getopt('debug') || @klass.debug
|
145
|
-
@dont_clean = getopt('dont_clean') || @klass.dont_clean
|
146
|
-
@poll_retries = getopt('poll_retries') || @klass.poll_retries
|
147
|
-
@poll_max_sleep = getopt('poll_max_sleep') || @klass.poll_max_sleep
|
148
|
-
|
149
|
-
@sleep_cycle = SleepCycle.new @min_sleep, @max_sleep, @sleep_inc
|
150
|
-
|
151
|
-
@clean = @dont_clean ? nil : lambda{File.unlink @path rescue nil}
|
152
|
-
@dirname = File.dirname @path
|
153
|
-
@basename = File.basename @path
|
154
|
-
@thief = false
|
155
|
-
@locked = false
|
156
|
-
|
157
|
-
lock(&block) if block
|
158
|
-
#}}}
|
159
|
-
end
|
160
|
-
def lock
|
161
|
-
#{{{
|
162
|
-
raise StackingLockError, "<#{ @path }> is locked!" if @locked
|
163
|
-
|
164
|
-
ret = nil
|
165
|
-
|
166
|
-
begin
|
167
|
-
@sleep_cycle.reset
|
168
|
-
create_tmplock do |f|
|
169
|
-
begin
|
170
|
-
Timeout::timeout(@timeout) do
|
171
|
-
tmp_path = f.path
|
172
|
-
tmp_stat = f.lstat
|
173
|
-
n_retries = 0
|
174
|
-
#sleeptime = @sleep_inc
|
175
|
-
|
176
|
-
trace{ "attempting to lock <#{ @path }>..." }
|
177
|
-
begin
|
178
|
-
i = 0
|
179
|
-
begin
|
180
|
-
trace{ "polling attempt <#{ i }>..." }
|
181
|
-
File.link tmp_path, @path
|
182
|
-
lock_stat = File.lstat @path
|
183
|
-
raise StatLockError, "stat's do not agree" unless
|
184
|
-
tmp_stat.rdev == lock_stat.rdev and tmp_stat.ino == lock_stat.ino
|
185
|
-
trace{ "aquired lock <#{ @path }>" }
|
186
|
-
@locked = true
|
187
|
-
rescue => e
|
188
|
-
i += 1
|
189
|
-
unless i >= @poll_retries
|
190
|
-
t = [rand(@poll_max_sleep), @poll_max_sleep].min
|
191
|
-
trace{ "poll sleep <#{ t }>..." }
|
192
|
-
sleep t
|
193
|
-
retry
|
194
|
-
end
|
195
|
-
raise
|
196
|
-
end
|
197
|
-
|
198
|
-
rescue => e
|
199
|
-
n_retries += 1
|
200
|
-
trace{ "n_retries <#{ n_retries }>" }
|
201
|
-
raise MaxTriesLockError, "surpased retries <#{ @retries }>" if
|
202
|
-
@retries and n_retries >= @retries
|
203
|
-
|
204
|
-
valid = validlock?
|
205
|
-
|
206
|
-
case valid
|
207
|
-
when true
|
208
|
-
trace{ "found valid lock" }
|
209
|
-
sleeptime = @sleep_cycle.next
|
210
|
-
trace{ "sleep <#{ sleeptime }>..." }
|
211
|
-
sleep sleeptime
|
212
|
-
when false
|
213
|
-
trace{ "found invalid lock and removing" }
|
214
|
-
begin
|
215
|
-
File.unlink @path
|
216
|
-
@thief = true
|
217
|
-
warn "<#{ @path }> stolen by <#{ Process.pid }> at <#{ timestamp }>"
|
218
|
-
trace{ "i am a thief!" }
|
219
|
-
trace{ "suspending <#{ @suspend }>" }
|
220
|
-
sleep @suspend
|
221
|
-
rescue Errno::ENOENT
|
222
|
-
end
|
223
|
-
when nil
|
224
|
-
# nothing
|
225
|
-
end
|
226
|
-
|
227
|
-
retry
|
228
|
-
end # begin
|
229
|
-
end # timeout
|
230
|
-
rescue Timeout::Error
|
231
|
-
raise TimeoutLockError, "surpassed timeout <#{ @timeout }>"
|
232
|
-
end # begin
|
233
|
-
end # create_tmplock
|
234
|
-
|
235
|
-
if block_given?
|
236
|
-
stolen = false
|
237
|
-
refresher = (@refresh ? new_refresher : nil)
|
238
|
-
begin
|
239
|
-
begin
|
240
|
-
ret = yield @path
|
241
|
-
rescue StolenLockError
|
242
|
-
stolen = true
|
243
|
-
raise
|
244
|
-
end
|
245
|
-
ensure
|
246
|
-
begin
|
247
|
-
refresher.kill if refresher and refresher.status
|
248
|
-
ensure
|
249
|
-
unlock unless stolen
|
250
|
-
end
|
251
|
-
end
|
252
|
-
else
|
253
|
-
ObjectSpace.define_finalizer self, @clean if @clean
|
254
|
-
ret = self
|
255
|
-
end
|
256
|
-
rescue Errno::ESTALE, Errno::EIO => e
|
257
|
-
raise(NFSLockError, errmsg(e))
|
258
|
-
end
|
259
|
-
|
260
|
-
return ret
|
261
|
-
#}}}
|
262
|
-
end
|
263
|
-
def unlock
|
264
|
-
#{{{
|
265
|
-
raise UnLockError, "<#{ @path }> is not locked!" unless @locked
|
266
|
-
begin
|
267
|
-
File.unlink @path
|
268
|
-
@locked = false
|
269
|
-
ObjectSpace.undefine_finalizer self if @clean
|
270
|
-
rescue Errno::ENOENT
|
271
|
-
@locked = false
|
272
|
-
ObjectSpace.undefine_finalizer self if @clean
|
273
|
-
raise StolenLockError, @path
|
274
|
-
end
|
275
|
-
#}}}
|
276
|
-
end
|
277
|
-
def new_refresher
|
278
|
-
#{{{
|
279
|
-
Thread.new(Thread.current, @path, @refresh) do |thread, path, refresh|
|
280
|
-
loop do
|
281
|
-
touch path
|
282
|
-
trace{"touched <#{ path }> @ <#{ Time.now.to_f }>"}
|
283
|
-
begin
|
284
|
-
loaded = load_lock_id(IO.read(path))
|
285
|
-
trace{"loaded <\n#{ loaded.inspect }\n>"}
|
286
|
-
raise unless loaded == @lock_id
|
287
|
-
rescue => e
|
288
|
-
trace{errmsg e}
|
289
|
-
thread.raise StolenLockError
|
290
|
-
Thread.exit
|
291
|
-
end
|
292
|
-
sleep refresh
|
293
|
-
end
|
294
|
-
end
|
295
|
-
#}}}
|
296
|
-
end
|
297
|
-
def validlock?
|
298
|
-
#{{{
|
299
|
-
if @max_age
|
300
|
-
uncache @path rescue nil
|
301
|
-
begin
|
302
|
-
return((Time.now - File.stat(@path).mtime) < @max_age)
|
303
|
-
rescue Errno::ENOENT
|
304
|
-
return nil
|
305
|
-
end
|
306
|
-
else
|
307
|
-
exist = File.exist?(@path)
|
308
|
-
return(exist ? true : nil)
|
309
|
-
end
|
310
|
-
#}}}
|
311
|
-
end
|
312
|
-
def uncache file
|
313
|
-
#{{{
|
314
|
-
refresh = nil
|
315
|
-
begin
|
316
|
-
is_a_file = File === file
|
317
|
-
path = (is_a_file ? file.path : file.to_s)
|
318
|
-
stat = (is_a_file ? file.stat : File.stat(file.to_s))
|
319
|
-
refresh = tmpnam(File.dirname(path))
|
320
|
-
File.link path, refresh
|
321
|
-
File.chmod stat.mode, path
|
322
|
-
File.utime stat.atime, stat.mtime, path
|
323
|
-
ensure
|
324
|
-
begin
|
325
|
-
File.unlink refresh if refresh
|
326
|
-
rescue Errno::ENOENT
|
327
|
-
end
|
328
|
-
end
|
329
|
-
#}}}
|
330
|
-
end
|
331
|
-
def create_tmplock
|
332
|
-
#{{{
|
333
|
-
tmplock = tmpnam @dirname
|
334
|
-
begin
|
335
|
-
create(tmplock) do |f|
|
336
|
-
@lock_id = gen_lock_id
|
337
|
-
dumped = dump_lock_id
|
338
|
-
trace{"lock_id <\n#{ @lock_id.inspect }\n>"}
|
339
|
-
f.write dumped
|
340
|
-
f.flush
|
341
|
-
yield f
|
342
|
-
end
|
343
|
-
ensure
|
344
|
-
begin; File.unlink tmplock; rescue Errno::ENOENT; end if tmplock
|
345
|
-
end
|
346
|
-
#}}}
|
347
|
-
end
|
348
|
-
def gen_lock_id
|
349
|
-
#{{{
|
350
|
-
Hash[
|
351
|
-
'host' => "#{ HOSTNAME }",
|
352
|
-
'pid' => "#{ Process.pid }",
|
353
|
-
'ppid' => "#{ Process.ppid }",
|
354
|
-
'time' => timestamp,
|
355
|
-
]
|
356
|
-
#}}}
|
357
|
-
end
|
358
|
-
def timestamp
|
359
|
-
#{{{
|
360
|
-
time = Time.now
|
361
|
-
usec = time.usec.to_s
|
362
|
-
usec << '0' while usec.size < 6
|
363
|
-
"#{ time.strftime('%Y-%m-%d %H:%M:%S') }.#{ usec }"
|
364
|
-
#}}}
|
365
|
-
end
|
366
|
-
def dump_lock_id lock_id = @lock_id
|
367
|
-
#{{{
|
368
|
-
"host: %s\npid: %s\nppid: %s\ntime: %s\n" %
|
369
|
-
lock_id.values_at('host','pid','ppid','time')
|
370
|
-
#}}}
|
371
|
-
end
|
372
|
-
def load_lock_id buf
|
373
|
-
#{{{
|
374
|
-
lock_id = {}
|
375
|
-
kv = %r/([^:]+):(.*)/o
|
376
|
-
buf.each do |line|
|
377
|
-
m = kv.match line
|
378
|
-
k, v = m[1], m[2]
|
379
|
-
next unless m and k and v
|
380
|
-
lock_id[k.strip] = v.strip
|
381
|
-
end
|
382
|
-
lock_id
|
383
|
-
#}}}
|
384
|
-
end
|
385
|
-
def tmpnam dir, seed = File.basename($0)
|
386
|
-
#{{{
|
387
|
-
pid = Process.pid
|
388
|
-
time = Time.now
|
389
|
-
sec = time.to_i
|
390
|
-
usec = time.usec
|
391
|
-
"%s%s.%s_%d_%s_%d_%d_%d" %
|
392
|
-
[dir, File::SEPARATOR, HOSTNAME, pid, seed, sec, usec, rand(sec)]
|
393
|
-
#}}}
|
394
|
-
end
|
395
|
-
def create path
|
396
|
-
#{{{
|
397
|
-
umask = nil
|
398
|
-
f = nil
|
399
|
-
begin
|
400
|
-
umask = File.umask 022
|
401
|
-
f = open path, File::WRONLY|File::CREAT|File::EXCL, 0644
|
402
|
-
ensure
|
403
|
-
File.umask umask if umask
|
404
|
-
end
|
405
|
-
return(block_given? ? begin; yield f; ensure; f.close; end : f)
|
406
|
-
#}}}
|
407
|
-
end
|
408
|
-
def touch path
|
409
|
-
#{{{
|
410
|
-
FileUtils.touch path
|
411
|
-
#}}}
|
412
|
-
end
|
413
|
-
def getopt key
|
414
|
-
#{{{
|
415
|
-
@opts[key] || @opts[key.to_s] || @opts[key.to_s.intern]
|
416
|
-
#}}}
|
417
|
-
end
|
418
|
-
def to_str
|
419
|
-
#{{{
|
420
|
-
@path
|
421
|
-
#}}}
|
422
|
-
end
|
423
|
-
alias to_s to_str
|
424
|
-
def trace s = nil
|
425
|
-
#{{{
|
426
|
-
STDERR.puts((s ? s : yield)) if @debug
|
427
|
-
#}}}
|
428
|
-
end
|
429
|
-
def errmsg e
|
430
|
-
#{{{
|
431
|
-
"%s: %s\n%s\n" % [e.class, e.message, e.backtrace.join("\n")]
|
432
|
-
#}}}
|
433
|
-
end
|
434
|
-
#}}}
|
435
|
-
end
|
436
|
-
$__lockfile__ == __FILE__
|
437
|
-
end
|
data/lockfile-1.1.0.gem
DELETED
File without changes
|
data/lockfile.gemspec
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'date'
|
2
|
-
Gem::Specification.new do |s|
|
3
|
-
s.name = %q{lockfile}
|
4
|
-
s.version = "1.1.0"
|
5
|
-
s.date = Date.today.to_s
|
6
|
-
s.summary = %q{A ruby library for creating NFS safe lockfiles}
|
7
|
-
s.description =<<DESCRIPTION
|
8
|
-
A ruby library for creating NFS safe lockfiles
|
9
|
-
DESCRIPTION
|
10
|
-
s.author = %q{-a}
|
11
|
-
s.email = %q{Ara.T.Howard@noaa.gov}
|
12
|
-
s.homepage = %q{http://www.codeforpeople.com/lib/ruby/lockfile/}
|
13
|
-
s.files = Dir.glob('**/*')
|
14
|
-
s.require_paths = %w{. lib}
|
15
|
-
s.autorequire = %q{lockfile}
|
16
|
-
s.has_rdoc = true
|
17
|
-
s.rdoc_options = ["--main", "README"]
|
18
|
-
s.extra_rdoc_files = ["README", "doc/rlock.help"]
|
19
|
-
s.executables = %w{rlock}
|
20
|
-
s.bindir = %q{bin}
|
21
|
-
s.platform = Gem::Platform::RUBY
|
22
|
-
end
|