lockfile 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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