lockfile 1.1.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/BUGS +3 -0
- data/README +165 -0
- data/VERSION +1 -0
- data/bin/rlock +326 -0
- data/doc/rlock.help +88 -0
- data/install.rb +143 -0
- data/lib/lockfile-1.1.0.rb +437 -0
- data/lib/lockfile.rb +437 -0
- data/lockfile-1.1.0.gem +0 -0
- data/lockfile.gemspec +22 -0
- data/samples/a.rb +112 -0
- data/samples/nfsstore.rb +203 -0
- data/samples/test.db +0 -0
- data/samples/test.db~ +0 -0
- metadata +59 -0
data/doc/rlock.help
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
= NAME
|
2
|
+
rlock v0.0.0
|
3
|
+
|
4
|
+
= SYNOPSIS
|
5
|
+
rlock [options]+ file.lock [program [-- [options]+] [args]+]
|
6
|
+
|
7
|
+
= DESCRIPTTION
|
8
|
+
rlock creates NFS resistent lockfiles
|
9
|
+
|
10
|
+
= ENVIRONMENT
|
11
|
+
LOCKFILE_DEBUG=1 will show internal actions of the library
|
12
|
+
|
13
|
+
= DIAGNOSTICS
|
14
|
+
success => $? == 0
|
15
|
+
failure => $? != 0
|
16
|
+
|
17
|
+
= AUTHOR
|
18
|
+
ara.t.howard@noaa.gov
|
19
|
+
|
20
|
+
= BUGS
|
21
|
+
> 1
|
22
|
+
|
23
|
+
= OPTIONS
|
24
|
+
|
25
|
+
-r, --retries=n
|
26
|
+
-a, --max_age=n
|
27
|
+
-s, --sleep_inc=n
|
28
|
+
-p, --max_sleep=n
|
29
|
+
-u, --suspend=n
|
30
|
+
-t, --timeout=n
|
31
|
+
-f, --refresh=n
|
32
|
+
-d, --debug
|
33
|
+
-v=0-4|debug|info|warn|error|fatal
|
34
|
+
--verbosity
|
35
|
+
-l, --log=path
|
36
|
+
--log_age=log_age
|
37
|
+
--log_size=log_size
|
38
|
+
-h, --help
|
39
|
+
|
40
|
+
= EXAMPLES
|
41
|
+
|
42
|
+
0) simple usage - just create a file.lock in an atomic fashion
|
43
|
+
|
44
|
+
~ > rlock file.lock
|
45
|
+
|
46
|
+
1) safe usage - create a file.lock, execute a command, and remove file.lock
|
47
|
+
|
48
|
+
~ > rlock file.lock ls file.lock
|
49
|
+
|
50
|
+
2) same as above, but logging verbose messages
|
51
|
+
|
52
|
+
~ > rlock -v4 file.lock ls file.lock
|
53
|
+
|
54
|
+
3) same as above, but logging verbose messages and showing actions internal to
|
55
|
+
lockfile library
|
56
|
+
|
57
|
+
~ > rlock -v4 -d file.lock ls file.lock
|
58
|
+
|
59
|
+
4) same as above
|
60
|
+
|
61
|
+
~ > LOCKFILE_DEBUG=1 rlock -v4 file.lock ls file.lock
|
62
|
+
|
63
|
+
5) same as above
|
64
|
+
|
65
|
+
~ > export LOCKFILE_DEBUG=1
|
66
|
+
~ > rlock -v4 file.lock ls file.lock
|
67
|
+
|
68
|
+
6) note that you need to tell the option parser to stop parsing rlock options if
|
69
|
+
you intend to pass options to 'program'
|
70
|
+
|
71
|
+
~ > rlock -v4 -d file.lock -- ls -ltar file.lock
|
72
|
+
|
73
|
+
without the '--' rlock would consume the '-ltar' options, parsing it
|
74
|
+
as the logfile name 'tar'
|
75
|
+
|
76
|
+
7) lock file.lock and exec 'program' - remove the file.lock if it is older than 4242 seconds
|
77
|
+
|
78
|
+
~ > rlock --max_age=4242 file.lock program
|
79
|
+
|
80
|
+
8) lock file.lock and exec 'program' - remove the file.lock if it is older
|
81
|
+
than 4242 seconds, also spawn a background thread which will refresh
|
82
|
+
file.lock every 8 seonds will 'program' is executing
|
83
|
+
|
84
|
+
~ > rlock --max_age=4242 --refresh=8 file.lock program
|
85
|
+
|
86
|
+
9) same as above, but fail if file.lock cannot be obtained within 1 minute
|
87
|
+
|
88
|
+
~ > rlock --max_age=4242 --refresh=8 --timeout=60 file.lock program
|
data/install.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rbconfig'
|
3
|
+
require 'find'
|
4
|
+
require 'ftools'
|
5
|
+
require 'tempfile'
|
6
|
+
include Config
|
7
|
+
|
8
|
+
LIBDIR = "lib"
|
9
|
+
LIBDIR_MODE = 0644
|
10
|
+
|
11
|
+
BINDIR = "bin"
|
12
|
+
BINDIR_MODE = 0755
|
13
|
+
|
14
|
+
|
15
|
+
$srcdir = CONFIG["srcdir"]
|
16
|
+
$version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
|
17
|
+
$libdir = File.join(CONFIG["libdir"], "ruby", $version)
|
18
|
+
$archdir = File.join($libdir, CONFIG["arch"])
|
19
|
+
$site_libdir = $:.find {|x| x =~ /site_ruby$/}
|
20
|
+
$bindir = CONFIG["bindir"]
|
21
|
+
$ruby_install_name = CONFIG['ruby_install_name'] || CONFIG['RUBY_INSTALL_NAME']
|
22
|
+
$ruby = File.join($bindir, $ruby_install_name || 'ruby')
|
23
|
+
|
24
|
+
if !$site_libdir
|
25
|
+
$site_libdir = File.join($libdir, "site_ruby")
|
26
|
+
elsif $site_libdir !~ %r/#{Regexp.quote($version)}/
|
27
|
+
$site_libdir = File.join($site_libdir, $version)
|
28
|
+
end
|
29
|
+
|
30
|
+
def install_rb(srcdir=nil, destdir=nil, mode=nil, bin=nil)
|
31
|
+
#{{{
|
32
|
+
path = []
|
33
|
+
dir = []
|
34
|
+
Find.find(srcdir) do |f|
|
35
|
+
next unless FileTest.file?(f)
|
36
|
+
next if (f = f[srcdir.length+1..-1]) == nil
|
37
|
+
next if (/CVS$/ =~ File.dirname(f))
|
38
|
+
path.push f
|
39
|
+
dir |= [File.dirname(f)]
|
40
|
+
end
|
41
|
+
for f in dir
|
42
|
+
next if f == "."
|
43
|
+
next if f == "CVS"
|
44
|
+
File::makedirs(File.join(destdir, f))
|
45
|
+
end
|
46
|
+
for f in path
|
47
|
+
next if (/\~$/ =~ f)
|
48
|
+
next if (/^\./ =~ File.basename(f))
|
49
|
+
unless bin
|
50
|
+
File::install(File.join(srcdir, f), File.join(destdir, f), mode, true)
|
51
|
+
else
|
52
|
+
from = File.join(srcdir, f)
|
53
|
+
to = File.join(destdir, f)
|
54
|
+
shebangify(from) do |sf|
|
55
|
+
$deferr.print from, " -> ", File::catname(from, to), "\n"
|
56
|
+
$deferr.printf "chmod %04o %s\n", mode, to
|
57
|
+
File::install(sf, to, mode, false)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
#}}}
|
62
|
+
end
|
63
|
+
def shebangify f
|
64
|
+
#{{{
|
65
|
+
open(f) do |fd|
|
66
|
+
buf = fd.read 42
|
67
|
+
if buf =~ %r/^\s*#\s*!.*ruby/o
|
68
|
+
ftmp = Tempfile::new("#{ $$ }_#{ File::basename(f) }")
|
69
|
+
begin
|
70
|
+
fd.rewind
|
71
|
+
ftmp.puts "#!#{ $ruby }"
|
72
|
+
while((buf = fd.read(8192)))
|
73
|
+
ftmp.write buf
|
74
|
+
end
|
75
|
+
ftmp.close
|
76
|
+
yield ftmp.path
|
77
|
+
ensure
|
78
|
+
ftmp.close!
|
79
|
+
end
|
80
|
+
else
|
81
|
+
yield f
|
82
|
+
end
|
83
|
+
end
|
84
|
+
#}}}
|
85
|
+
end
|
86
|
+
def ARGV.switch
|
87
|
+
#{{{
|
88
|
+
return nil if self.empty?
|
89
|
+
arg = self.shift
|
90
|
+
return nil if arg == '--'
|
91
|
+
if arg =~ /^-(.)(.*)/
|
92
|
+
return arg if $1 == '-'
|
93
|
+
raise 'unknown switch "-"' if $2.index('-')
|
94
|
+
self.unshift "-#{$2}" if $2.size > 0
|
95
|
+
"-#{$1}"
|
96
|
+
else
|
97
|
+
self.unshift arg
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
#}}}
|
101
|
+
end
|
102
|
+
def ARGV.req_arg
|
103
|
+
#{{{
|
104
|
+
self.shift || raise('missing argument')
|
105
|
+
#}}}
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
#
|
110
|
+
# main program
|
111
|
+
#
|
112
|
+
|
113
|
+
libdir = $site_libdir
|
114
|
+
bindir = $bindir
|
115
|
+
|
116
|
+
begin
|
117
|
+
while switch = ARGV.switch
|
118
|
+
case switch
|
119
|
+
when '-d', '--destdir'
|
120
|
+
libdir = ARGV.req_arg
|
121
|
+
when '-l', '--libdir'
|
122
|
+
libdir = ARGV.req_arg
|
123
|
+
when '-b', '--bindir'
|
124
|
+
bindir = ARGV.req_arg
|
125
|
+
when '-r', '--ruby'
|
126
|
+
$ruby = ARGV.req_arg
|
127
|
+
else
|
128
|
+
raise "unknown switch #{switch.dump}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
rescue
|
132
|
+
STDERR.puts $!.to_s
|
133
|
+
STDERR.puts File.basename($0) +
|
134
|
+
" -d <destdir>" +
|
135
|
+
" -l <libdir>" +
|
136
|
+
" -b <bindir>"
|
137
|
+
" -r <ruby>"
|
138
|
+
exit 1
|
139
|
+
end
|
140
|
+
|
141
|
+
install_rb(LIBDIR, libdir, LIBDIR_MODE)
|
142
|
+
install_rb(BINDIR, bindir, BINDIR_MODE, bin=true)
|
143
|
+
|
@@ -0,0 +1,437 @@
|
|
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
|