lockfile 1.4.0 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +221 -0
- data/bin/rlock +23 -5
- data/bin/{rlock-1.3.0 → rlock-1.4.1} +23 -5
- data/doc/rlock.help +95 -0
- data/gemspec.rb +23 -0
- data/install.rb +206 -0
- data/lib/{lockfile-1.4.0.rb → lockfile-1.4.1.rb} +26 -18
- data/lib/lockfile.rb +26 -18
- data/rlock +2 -0
- data/samples/a.rb +112 -0
- data/samples/lock +4 -0
- data/samples/lock.sh +13 -0
- data/samples/lockfile +4 -0
- data/samples/nfsstore.rb +203 -0
- data/samples/out +80 -0
- metadata +22 -5
data/README
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
URLS
|
2
|
+
|
3
|
+
http://rubyforge.org/projects/codeforpeople/
|
4
|
+
http://codeforpeople.com/lib/ruby/lockfile/
|
5
|
+
http://raa.ruby-lang.org/project/lockfile/
|
6
|
+
|
7
|
+
SYNOPSIS
|
8
|
+
|
9
|
+
lib/lockfile.rb : a ruby library for creating NFS safe lockfiles
|
10
|
+
|
11
|
+
bin/rlock : ruby command line tool which uses this library to create lockfiles
|
12
|
+
and to run arbitrary commands while holding them.
|
13
|
+
|
14
|
+
for example
|
15
|
+
|
16
|
+
rlock lockfile -- cp -r huge/ huge.bak/
|
17
|
+
|
18
|
+
run 'rlock -h' for more info
|
19
|
+
|
20
|
+
INSTALL
|
21
|
+
|
22
|
+
sudo ruby install.rb
|
23
|
+
|
24
|
+
|
25
|
+
BASIC ALGORITHIM
|
26
|
+
|
27
|
+
* create a globally uniq filename in the same filesystem as the desired
|
28
|
+
lockfile - this can be nfs mounted
|
29
|
+
|
30
|
+
* link(2) this file to the desired lockfile, ignore all errors
|
31
|
+
|
32
|
+
* stat the uniq filename and desired lockfile to determine is they are the
|
33
|
+
same, use only stat.rdev and stat.ino - ignore stat.nlink as NFS can cause
|
34
|
+
this report incorrect values
|
35
|
+
|
36
|
+
* iff same, you have lock. either return or run optional code block with
|
37
|
+
optional refresher thread keeping lockfile fresh during execution of code
|
38
|
+
block, ensuring that the lockfile is removed..
|
39
|
+
|
40
|
+
* iff not same try again a few times in rapid succession (poll), then, iff
|
41
|
+
this fails, sleep using incremental backoff time. optionally remove
|
42
|
+
lockfile if it is older than a certain time, timeout if more than a certain
|
43
|
+
amount of time has passed attempting to lock file.
|
44
|
+
|
45
|
+
|
46
|
+
BASIC USAGE
|
47
|
+
|
48
|
+
1)
|
49
|
+
lockfile = Lockfile.new 'file.lock'
|
50
|
+
begin
|
51
|
+
lockfile.lock
|
52
|
+
p 42
|
53
|
+
ensure
|
54
|
+
lockfile.unlock
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
2)
|
59
|
+
require 'pstore' # which is NOT nfs safe on it's own
|
60
|
+
|
61
|
+
opts = { # the keys can be symbols or strings
|
62
|
+
|
63
|
+
:retries => nil, # we will try forever to aquire the lock
|
64
|
+
|
65
|
+
:sleep_inc => 2, # we will sleep 2 seconds longer than the
|
66
|
+
# previous sleep after each retry, cycling from
|
67
|
+
# min_sleep upto max_sleep downto min_sleep upto
|
68
|
+
# max_sleep, etc., etc.
|
69
|
+
|
70
|
+
:min_sleep => 2, # we will never sleep less than 2 seconds
|
71
|
+
|
72
|
+
:max_sleep => 32, # we will never sleep longer than 32 seconds
|
73
|
+
|
74
|
+
:max_age => 1024, # we will blow away any files found to be older
|
75
|
+
# than this (lockfile.thief? #=> true)
|
76
|
+
|
77
|
+
:suspend => 64, # iff we steal the lock from someone else - wait
|
78
|
+
# this long to give them a chance to realize it
|
79
|
+
|
80
|
+
:refresh => 8, # we will spawn a bg thread that touches file
|
81
|
+
# every 8 sec. this thread also causes a
|
82
|
+
# StolenLockError to be thrown if the lock
|
83
|
+
# disappears from under us - note that the
|
84
|
+
# 'detection' rate is limited to the refresh
|
85
|
+
# interval - this is a race condition
|
86
|
+
|
87
|
+
:timeout => nil, # we will wait forever
|
88
|
+
|
89
|
+
:poll_retries => 16, # the initial attempt to grab a lock is done in a
|
90
|
+
# polling fashion, this number controls how many
|
91
|
+
# times this is done - the total polling attempts
|
92
|
+
# are considered ONE actual attempt (see retries
|
93
|
+
# above)
|
94
|
+
|
95
|
+
:poll_max_sleep => 0.08, # when polling a very brief sleep is issued
|
96
|
+
# between attempts, this is the upper limit of
|
97
|
+
# that sleep timeout
|
98
|
+
|
99
|
+
:dont_clean => false, # normally a finalizer is defined to clean up
|
100
|
+
# after lockfiles, settin this to true prevents this
|
101
|
+
|
102
|
+
:dont_sweep => false, # normally locking causes a sweep to be made. a
|
103
|
+
# sweep removes any old tmp files created by
|
104
|
+
# processes of this host only which are no
|
105
|
+
# longer alive
|
106
|
+
|
107
|
+
:debug => true, # trace execution step on stdout
|
108
|
+
}
|
109
|
+
|
110
|
+
pstore = PStore.new 'file.db'
|
111
|
+
lockfile = Lockfile.new 'file.db.lock', opts
|
112
|
+
lockfile.lock do
|
113
|
+
pstore.transaction do
|
114
|
+
pstore[:last_update_time] = Time.now
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
3) same as 1 above - Lockfile.new takes a block and ensures unlock is called
|
119
|
+
|
120
|
+
Lockfile.new('file.lock') do
|
121
|
+
p 42
|
122
|
+
end
|
123
|
+
|
124
|
+
4) watch locking algorithim in action (debugging only)
|
125
|
+
|
126
|
+
Lockfile.debug = true
|
127
|
+
Lockfile.new('file.lock') do
|
128
|
+
p 42
|
129
|
+
end
|
130
|
+
|
131
|
+
you can also set debugging via the ENV var LOCKFILE_DEBUG, eg.
|
132
|
+
|
133
|
+
~ > LOCKFILE_DEBUG=true rlock lockfile
|
134
|
+
|
135
|
+
5) simplified interface : no lockfile object required
|
136
|
+
|
137
|
+
Lockfile('lock', :retries => 0) do
|
138
|
+
puts 'only one instance running!'
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
SAMPLES
|
143
|
+
|
144
|
+
* see samples/a.rb
|
145
|
+
* see samples/nfsstore.rb
|
146
|
+
* see samples/lock.sh
|
147
|
+
* see bin/rlock
|
148
|
+
|
149
|
+
AUTHOR
|
150
|
+
|
151
|
+
Ara T. Howard
|
152
|
+
|
153
|
+
EMAIL
|
154
|
+
|
155
|
+
Ara.T.Howard@noaa.gov
|
156
|
+
|
157
|
+
BUGS
|
158
|
+
|
159
|
+
bugno > 1 && bugno < 42
|
160
|
+
|
161
|
+
HISTORY
|
162
|
+
|
163
|
+
1.4.1:
|
164
|
+
- Mike Kasick <mkasick@club.cc.cmu.edu> reported a bug whereby false/nil
|
165
|
+
values for options were ignored. patched to address this bug
|
166
|
+
- added Lockfile method for high level interface sans lockfile object
|
167
|
+
- updated rlock program to allow nil/true/false values passed on command
|
168
|
+
line. eg
|
169
|
+
|
170
|
+
rlock --max_age=nil lockfile -- date --iso-8601=seconds
|
171
|
+
|
172
|
+
1.4.0:
|
173
|
+
- gem'd up
|
174
|
+
- added Lockfile::create method which atomically creates a file and opens
|
175
|
+
it:
|
176
|
+
|
177
|
+
Lockfile::create("atomic_even_on_nfs", "r+") do |f|
|
178
|
+
f.puts 42
|
179
|
+
end
|
180
|
+
|
181
|
+
arguments are the same as those for File::open. note that there is no way
|
182
|
+
to accomplish this otherwise since File::O_EXCL fails silently on nfs,
|
183
|
+
flock does not work on nfs, and posixlock (see raa) can only lock a file
|
184
|
+
after it is open so the open itself is still a race.
|
185
|
+
|
186
|
+
1.3.0:
|
187
|
+
- added sweep functionality. hosts can clean up any tmp files left around
|
188
|
+
by other processes that may have been killed using -9 to prevent proper
|
189
|
+
clean up. it is only possible to clean up after processes on the same
|
190
|
+
host as there is no other way to determine if the process that created the
|
191
|
+
file is alive. in summary - hosts clean up after other processes on that
|
192
|
+
same host if needed.
|
193
|
+
- added attempt/try_again methods
|
194
|
+
1.2.0:
|
195
|
+
- fixed bug where stale locks not removed when retries == 0
|
196
|
+
1.1.0
|
197
|
+
- unfortunately i've forgotten
|
198
|
+
1.0.1:
|
199
|
+
- fixed bugette in sleep cycle where repeated locks with same lockfile would
|
200
|
+
not reset the cycle at the start of each lock
|
201
|
+
1.0.0:
|
202
|
+
- allow rertries to be nil, meaning to try forever
|
203
|
+
- default timeout is now nil - never timeout
|
204
|
+
- default refresh is now 8
|
205
|
+
- fixed bug where refresher thread was not actually touching lockfile! (ouch)
|
206
|
+
- added cycle method to timeouts
|
207
|
+
1-2-3-2-1-2-3-1...
|
208
|
+
pattern is constructed using min_sleep, sleep_inc, max_sleep
|
209
|
+
0.3.0:
|
210
|
+
- removed use of yaml in favour of hand parsing the lockfile contents, the
|
211
|
+
file as so small it just wasn't worth and i have had one core dump when yaml
|
212
|
+
failed to parse a (corrupt) file
|
213
|
+
0.2.0:
|
214
|
+
- added an initial polling style attempt to grab lock before entering normal
|
215
|
+
sleep/retry loop. this has really helped performance when lock is under
|
216
|
+
heavy contention: i see almost no sleeping done by any of in the interested
|
217
|
+
processes
|
218
|
+
0.1.0:
|
219
|
+
- added ability of Lockfile.new to accept a block
|
220
|
+
0.0.0:
|
221
|
+
- initial version
|
data/bin/rlock
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
#
|
8
8
|
# http://raa.ruby-lang.org/project/lockfile/
|
9
9
|
#
|
10
|
-
require 'lockfile-1.
|
10
|
+
require 'lockfile-1.4.1.rb'
|
11
11
|
|
12
12
|
class Main
|
13
13
|
#--{{{
|
@@ -159,10 +159,12 @@
|
|
159
159
|
options =
|
160
160
|
%w(retries max_age sleep_inc min_sleep max_sleep suspend timeout refresh poll_retries poll_max_sleep)
|
161
161
|
options.each do |opt|
|
162
|
-
if((val = eval("opt_#{ opt }")))
|
162
|
+
#if((val = eval("opt_#{ opt }")))
|
163
|
+
if(send("opt_#{ opt }?"))
|
164
|
+
val = send "opt_#{ opt }"
|
163
165
|
begin
|
164
|
-
opts[opt] = Integer val
|
165
|
-
logger.debug{ "<#{ opt }> <#{ val }>" }
|
166
|
+
val = (opts[opt] = String === val ? Integer(val) : val)
|
167
|
+
logger.debug{ "<#{ opt }> <#{ val.inspect }>" }
|
166
168
|
rescue
|
167
169
|
logger.fatal{ "illegal value <#{ val.inspect }> for opt <#{ opt }>" }
|
168
170
|
exit EXIT_FAILURE
|
@@ -265,7 +267,21 @@
|
|
265
267
|
options.each do |option|
|
266
268
|
opt = option.first.gsub(%r/(?:--)|(?:=.*$)/o,'').strip
|
267
269
|
get, set = opt_attr opt
|
268
|
-
|
270
|
+
value4 = lambda do |v|
|
271
|
+
case v
|
272
|
+
when NilClass, %r/^t|true$/i
|
273
|
+
true
|
274
|
+
when %r/^f|false$/i
|
275
|
+
false
|
276
|
+
when %r/^nil|nul|null$/i
|
277
|
+
nil
|
278
|
+
else
|
279
|
+
v
|
280
|
+
end
|
281
|
+
end
|
282
|
+
@op.def_option(*option) do |v|
|
283
|
+
send set, value4[v]
|
284
|
+
end
|
269
285
|
end
|
270
286
|
#--}}}
|
271
287
|
end
|
@@ -319,10 +335,12 @@
|
|
319
335
|
end
|
320
336
|
def opt_attr opt
|
321
337
|
#--{{{
|
338
|
+
query = "opt_#{ opt }?"
|
322
339
|
get = "opt_#{ opt }"
|
323
340
|
set = "#{ get }="
|
324
341
|
code = <<-code
|
325
342
|
class << self
|
343
|
+
def #{ query }; defined? @#{ get }; end
|
326
344
|
def #{ get }; defined?(@#{ get }) ? @#{ get } : nil; end
|
327
345
|
def #{ set } value; @#{ get } = value; end
|
328
346
|
end
|
@@ -7,7 +7,7 @@
|
|
7
7
|
#
|
8
8
|
# http://raa.ruby-lang.org/project/lockfile/
|
9
9
|
#
|
10
|
-
require 'lockfile-1.
|
10
|
+
require 'lockfile-1.4.1.rb'
|
11
11
|
|
12
12
|
class Main
|
13
13
|
#--{{{
|
@@ -159,10 +159,12 @@
|
|
159
159
|
options =
|
160
160
|
%w(retries max_age sleep_inc min_sleep max_sleep suspend timeout refresh poll_retries poll_max_sleep)
|
161
161
|
options.each do |opt|
|
162
|
-
if((val = eval("opt_#{ opt }")))
|
162
|
+
#if((val = eval("opt_#{ opt }")))
|
163
|
+
if(send("opt_#{ opt }?"))
|
164
|
+
val = send "opt_#{ opt }"
|
163
165
|
begin
|
164
|
-
opts[opt] = Integer val
|
165
|
-
logger.debug{ "<#{ opt }> <#{ val }>" }
|
166
|
+
val = (opts[opt] = String === val ? Integer(val) : val)
|
167
|
+
logger.debug{ "<#{ opt }> <#{ val.inspect }>" }
|
166
168
|
rescue
|
167
169
|
logger.fatal{ "illegal value <#{ val.inspect }> for opt <#{ opt }>" }
|
168
170
|
exit EXIT_FAILURE
|
@@ -265,7 +267,21 @@
|
|
265
267
|
options.each do |option|
|
266
268
|
opt = option.first.gsub(%r/(?:--)|(?:=.*$)/o,'').strip
|
267
269
|
get, set = opt_attr opt
|
268
|
-
|
270
|
+
value4 = lambda do |v|
|
271
|
+
case v
|
272
|
+
when NilClass, %r/^t|true$/i
|
273
|
+
true
|
274
|
+
when %r/^f|false$/i
|
275
|
+
false
|
276
|
+
when %r/^nil|nul|null$/i
|
277
|
+
nil
|
278
|
+
else
|
279
|
+
v
|
280
|
+
end
|
281
|
+
end
|
282
|
+
@op.def_option(*option) do |v|
|
283
|
+
send set, value4[v]
|
284
|
+
end
|
269
285
|
end
|
270
286
|
#--}}}
|
271
287
|
end
|
@@ -319,10 +335,12 @@
|
|
319
335
|
end
|
320
336
|
def opt_attr opt
|
321
337
|
#--{{{
|
338
|
+
query = "opt_#{ opt }?"
|
322
339
|
get = "opt_#{ opt }"
|
323
340
|
set = "#{ get }="
|
324
341
|
code = <<-code
|
325
342
|
class << self
|
343
|
+
def #{ query }; defined? @#{ get }; end
|
326
344
|
def #{ get }; defined?(@#{ get }) ? @#{ get } : nil; end
|
327
345
|
def #{ set } value; @#{ get } = value; end
|
328
346
|
end
|
data/doc/rlock.help
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
NAME
|
2
|
+
rlock v1.3.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
|
+
|
26
|
+
-r, --retries=n default(nil) - (nil => forever)
|
27
|
+
-a, --max_age=n default(1024)
|
28
|
+
-s, --sleep_inc=n default(2)
|
29
|
+
-p, --max_sleep=n default(32)
|
30
|
+
-P, --min_sleep=n default(2)
|
31
|
+
-u, --suspend=n default(64)
|
32
|
+
-t, --timeout=n default(nil) - (nil => never)
|
33
|
+
-f, --refresh=n default(8)
|
34
|
+
-d, --debug default(false)
|
35
|
+
-R, --poll_retries=n default(16)
|
36
|
+
-S, --poll_max_sleep=n default(0.08)
|
37
|
+
-w, --dont_sweep default(false)
|
38
|
+
-v=0-4|debug|info|warn|error|fatal
|
39
|
+
--verbosity
|
40
|
+
-l, --log=path
|
41
|
+
--log_age=log_age
|
42
|
+
--log_size=log_size
|
43
|
+
-h, --help
|
44
|
+
|
45
|
+
EXAMPLES
|
46
|
+
|
47
|
+
0) simple usage - just create a file.lock in an atomic fashion
|
48
|
+
|
49
|
+
~ > rlock file.lock
|
50
|
+
|
51
|
+
1) safe usage - create a file.lock, execute a command, and remove file.lock
|
52
|
+
|
53
|
+
~ > rlock file.lock ls file.lock
|
54
|
+
|
55
|
+
2) same as above, but logging verbose messages
|
56
|
+
|
57
|
+
~ > rlock -v4 file.lock ls file.lock
|
58
|
+
|
59
|
+
3) same as above, but logging verbose messages and showing actions internal to
|
60
|
+
lockfile library
|
61
|
+
|
62
|
+
~ > rlock -v4 -d file.lock ls file.lock
|
63
|
+
|
64
|
+
4) same as above
|
65
|
+
|
66
|
+
~ > LOCKFILE_DEBUG=1 rlock -v4 file.lock ls file.lock
|
67
|
+
|
68
|
+
5) same as above
|
69
|
+
|
70
|
+
~ > export LOCKFILE_DEBUG=1
|
71
|
+
~ > rlock -v4 file.lock ls file.lock
|
72
|
+
|
73
|
+
6) note that you need to tell the option parser to stop parsing rlock
|
74
|
+
options if you intend to pass options to 'program'
|
75
|
+
|
76
|
+
~ > rlock -v4 -d file.lock -- ls -ltar file.lock
|
77
|
+
|
78
|
+
without the '--' rlock would consume the '-ltar' options, parsing it
|
79
|
+
as the logfile name 'tar'
|
80
|
+
|
81
|
+
7) lock file.lock and exec 'program' - remove the file.lock if it is older
|
82
|
+
than 4242 seconds
|
83
|
+
|
84
|
+
~ > rlock --max_age=4242 file.lock program
|
85
|
+
|
86
|
+
8) lock file.lock and exec 'program' - remove the file.lock if it is older
|
87
|
+
than 4242 seconds, also spawn a background thread which will refresh
|
88
|
+
file.lock every 8 seonds will 'program' is executing
|
89
|
+
|
90
|
+
~ > rlock --max_age=4242 --refresh=8 file.lock program
|
91
|
+
|
92
|
+
9) same as above, but fail if file.lock cannot be obtained within 1 minute
|
93
|
+
|
94
|
+
~ > rlock --max_age=4242 --refresh=8 --timeout=60 file.lock program
|
95
|
+
|
data/gemspec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
lib, version = File::basename(File::dirname(File::expand_path(__FILE__))).split %r/-/, 2
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
Gem::Specification::new do |spec|
|
6
|
+
spec.name = lib
|
7
|
+
spec.version = version
|
8
|
+
spec.platform = Gem::Platform::RUBY
|
9
|
+
spec.summary = lib
|
10
|
+
|
11
|
+
spec.files = Dir::glob "**/**"
|
12
|
+
spec.executables = Dir::glob("bin/*").map{|exe| File::basename exe}
|
13
|
+
|
14
|
+
spec.require_path = "lib"
|
15
|
+
spec.autorequire = lib
|
16
|
+
|
17
|
+
spec.has_rdoc = File::exist? "doc"
|
18
|
+
spec.test_suite_file = "test/#{ lib }.rb" if File::directory? "test"
|
19
|
+
|
20
|
+
spec.author = "Ara T. Howard"
|
21
|
+
spec.email = "ara.t.howard@noaa.gov"
|
22
|
+
spec.homepage = "http://codeforpeople.com/lib/ruby/#{ lib }/"
|
23
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1,206 @@
|
|
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"] || CONFIG['BINDIR']
|
21
|
+
$ruby_install_name = CONFIG['ruby_install_name'] || CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
|
22
|
+
$ruby_ext = CONFIG['EXEEXT'] || ''
|
23
|
+
$ruby = File.join($bindir, ($ruby_install_name + $ruby_ext))
|
24
|
+
|
25
|
+
if !$site_libdir
|
26
|
+
$site_libdir = File.join($libdir, "site_ruby")
|
27
|
+
elsif $site_libdir !~ %r/#{Regexp.quote($version)}/
|
28
|
+
$site_libdir = File.join($site_libdir, $version)
|
29
|
+
end
|
30
|
+
|
31
|
+
def install_rb(srcdir=nil, destdir=nil, mode=nil, bin=nil)
|
32
|
+
#{{{
|
33
|
+
path = []
|
34
|
+
dir = []
|
35
|
+
Find.find(srcdir) do |f|
|
36
|
+
next unless FileTest.file?(f)
|
37
|
+
next if (f = f[srcdir.length+1..-1]) == nil
|
38
|
+
next if (/CVS$/ =~ File.dirname(f))
|
39
|
+
next if f =~ %r/\.lnk/
|
40
|
+
path.push f
|
41
|
+
dir |= [File.dirname(f)]
|
42
|
+
end
|
43
|
+
for f in dir
|
44
|
+
next if f == "."
|
45
|
+
next if f == "CVS"
|
46
|
+
File::makedirs(File.join(destdir, f))
|
47
|
+
end
|
48
|
+
for f in path
|
49
|
+
next if (/\~$/ =~ f)
|
50
|
+
next if (/^\./ =~ File.basename(f))
|
51
|
+
unless bin
|
52
|
+
File::install(File.join(srcdir, f), File.join(destdir, f), mode, true)
|
53
|
+
else
|
54
|
+
from = File.join(srcdir, f)
|
55
|
+
to = File.join(destdir, f)
|
56
|
+
shebangify(from) do |sf|
|
57
|
+
$deferr.print from, " -> ", File::catname(from, to), "\n"
|
58
|
+
$deferr.printf "chmod %04o %s\n", mode, to
|
59
|
+
File::install(sf, to, mode, false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
#}}}
|
64
|
+
end
|
65
|
+
def shebangify f
|
66
|
+
#{{{
|
67
|
+
open(f) do |fd|
|
68
|
+
buf = fd.read 42
|
69
|
+
if buf =~ %r/^\s*#\s*!.*ruby/o
|
70
|
+
ftmp = Tempfile::new("#{ $$ }_#{ File::basename(f) }")
|
71
|
+
begin
|
72
|
+
fd.rewind
|
73
|
+
ftmp.puts "#!#{ $ruby }"
|
74
|
+
while((buf = fd.read(8192)))
|
75
|
+
ftmp.write buf
|
76
|
+
end
|
77
|
+
ftmp.close
|
78
|
+
yield ftmp.path
|
79
|
+
ensure
|
80
|
+
ftmp.close!
|
81
|
+
end
|
82
|
+
else
|
83
|
+
yield f
|
84
|
+
end
|
85
|
+
end
|
86
|
+
#}}}
|
87
|
+
end
|
88
|
+
def ARGV.switch
|
89
|
+
#{{{
|
90
|
+
return nil if self.empty?
|
91
|
+
arg = self.shift
|
92
|
+
return nil if arg == '--'
|
93
|
+
if arg =~ /^-(.)(.*)/
|
94
|
+
return arg if $1 == '-'
|
95
|
+
raise 'unknown switch "-"' if $2.index('-')
|
96
|
+
self.unshift "-#{$2}" if $2.size > 0
|
97
|
+
"-#{$1}"
|
98
|
+
else
|
99
|
+
self.unshift arg
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
#}}}
|
103
|
+
end
|
104
|
+
def ARGV.req_arg
|
105
|
+
#{{{
|
106
|
+
self.shift || raise('missing argument')
|
107
|
+
#}}}
|
108
|
+
end
|
109
|
+
def linkify d, linked = []
|
110
|
+
#--{{{
|
111
|
+
if test ?d, d
|
112
|
+
versioned = Dir[ File::join(d, "*-[0-9].[0-9].[0-9].rb") ]
|
113
|
+
versioned.each do |v|
|
114
|
+
src, dst = v, v.gsub(%r/\-[\d\.]+\.rb$/, '.rb')
|
115
|
+
lnk = nil
|
116
|
+
begin
|
117
|
+
if test ?l, dst
|
118
|
+
lnk = "#{ dst }.lnk"
|
119
|
+
puts "#{ dst } -> #{ lnk }"
|
120
|
+
File::rename dst, lnk
|
121
|
+
end
|
122
|
+
unless test ?e, dst
|
123
|
+
puts "#{ src } -> #{ dst }"
|
124
|
+
File::copy src, dst
|
125
|
+
linked << dst
|
126
|
+
end
|
127
|
+
ensure
|
128
|
+
if lnk
|
129
|
+
at_exit do
|
130
|
+
puts "#{ lnk } -> #{ dst }"
|
131
|
+
File::rename lnk, dst
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
linked
|
138
|
+
#--}}}
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
#
|
143
|
+
# main program
|
144
|
+
#
|
145
|
+
|
146
|
+
libdir = $site_libdir
|
147
|
+
bindir = $bindir
|
148
|
+
no_linkify = false
|
149
|
+
linked = nil
|
150
|
+
help = false
|
151
|
+
|
152
|
+
usage = <<-usage
|
153
|
+
#{ File::basename $0 }
|
154
|
+
-d, --destdir <destdir>
|
155
|
+
-l, --libdir <libdir>
|
156
|
+
-b, --bindir <bindir>
|
157
|
+
-r, --ruby <ruby>
|
158
|
+
-n, --no_linkify
|
159
|
+
-s, --sudo
|
160
|
+
-h, --help
|
161
|
+
usage
|
162
|
+
|
163
|
+
begin
|
164
|
+
while switch = ARGV.switch
|
165
|
+
case switch
|
166
|
+
when '-d', '--destdir'
|
167
|
+
libdir = ARGV.req_arg
|
168
|
+
when '-l', '--libdir'
|
169
|
+
libdir = ARGV.req_arg
|
170
|
+
when '-b', '--bindir'
|
171
|
+
bindir = ARGV.req_arg
|
172
|
+
when '-r', '--ruby'
|
173
|
+
$ruby = ARGV.req_arg
|
174
|
+
when '-n', '--no_linkify'
|
175
|
+
no_linkify = true
|
176
|
+
when '-s', '--sudo'
|
177
|
+
sudo = 'sudo'
|
178
|
+
when '-h', '--help'
|
179
|
+
help = true
|
180
|
+
else
|
181
|
+
raise "unknown switch #{switch.dump}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
rescue
|
185
|
+
STDERR.puts $!.to_s
|
186
|
+
STDERR.puts usage
|
187
|
+
exit 1
|
188
|
+
end
|
189
|
+
|
190
|
+
if help
|
191
|
+
STDOUT.puts usage
|
192
|
+
exit
|
193
|
+
end
|
194
|
+
|
195
|
+
unless no_linkify
|
196
|
+
linked = linkify('lib') + linkify('bin')
|
197
|
+
end
|
198
|
+
|
199
|
+
system "#{ $ruby } extconf.rb && make && #{ sudo } make install" if test(?s, 'extconf.rb')
|
200
|
+
|
201
|
+
install_rb(LIBDIR, libdir, LIBDIR_MODE)
|
202
|
+
install_rb(BINDIR, bindir, BINDIR_MODE, bin=true)
|
203
|
+
|
204
|
+
if linked
|
205
|
+
linked.each{|path| File::rm_f path}
|
206
|
+
end
|
@@ -5,7 +5,7 @@ unless defined? $__lockfile__
|
|
5
5
|
|
6
6
|
class Lockfile
|
7
7
|
#--{{{
|
8
|
-
VERSION = '1.4.
|
8
|
+
VERSION = '1.4.1'
|
9
9
|
|
10
10
|
class LockError < StandardError; end
|
11
11
|
class StolenLockError < LockError; end
|
@@ -171,21 +171,20 @@ unless defined? $__lockfile__
|
|
171
171
|
@path = path
|
172
172
|
@opts = opts
|
173
173
|
|
174
|
-
@retries = getopt
|
175
|
-
@max_age = getopt
|
176
|
-
@sleep_inc = getopt
|
177
|
-
@min_sleep = getopt
|
178
|
-
@max_sleep = getopt
|
179
|
-
@suspend = getopt
|
180
|
-
@timeout = getopt
|
181
|
-
@refresh = getopt
|
182
|
-
@dont_clean = getopt
|
183
|
-
@poll_retries = getopt
|
184
|
-
@poll_max_sleep = getopt
|
185
|
-
@dont_sweep = getopt
|
186
|
-
@dont_use_lock_id = getopt
|
187
|
-
|
188
|
-
@debug = getopt('debug') || @klass.debug
|
174
|
+
@retries = getopt 'retries' , @klass.retries
|
175
|
+
@max_age = getopt 'max_age' , @klass.max_age
|
176
|
+
@sleep_inc = getopt 'sleep_inc' , @klass.sleep_inc
|
177
|
+
@min_sleep = getopt 'min_sleep' , @klass.min_sleep
|
178
|
+
@max_sleep = getopt 'max_sleep' , @klass.max_sleep
|
179
|
+
@suspend = getopt 'suspend' , @klass.suspend
|
180
|
+
@timeout = getopt 'timeout' , @klass.timeout
|
181
|
+
@refresh = getopt 'refresh' , @klass.refresh
|
182
|
+
@dont_clean = getopt 'dont_clean' , @klass.dont_clean
|
183
|
+
@poll_retries = getopt 'poll_retries' , @klass.poll_retries
|
184
|
+
@poll_max_sleep = getopt 'poll_max_sleep' , @klass.poll_max_sleep
|
185
|
+
@dont_sweep = getopt 'dont_sweep' , @klass.dont_sweep
|
186
|
+
@dont_use_lock_id = getopt 'dont_use_lock_id' , @klass.dont_use_lock_id
|
187
|
+
@debug = getopt 'debug' , @klass.debug
|
189
188
|
|
190
189
|
@sleep_cycle = SleepCycle::new @min_sleep, @max_sleep, @sleep_inc
|
191
190
|
|
@@ -508,9 +507,12 @@ unless defined? $__lockfile__
|
|
508
507
|
FileUtils.touch path
|
509
508
|
#--}}}
|
510
509
|
end
|
511
|
-
def getopt key
|
510
|
+
def getopt key, default = nil
|
512
511
|
#--{{{
|
513
|
-
|
512
|
+
[ key, key.to_s, key.to_s.intern ].each do |k|
|
513
|
+
return @opts[k] if @opts.has_key?(k)
|
514
|
+
end
|
515
|
+
return default
|
514
516
|
#--}}}
|
515
517
|
end
|
516
518
|
def to_str
|
@@ -550,5 +552,11 @@ unless defined? $__lockfile__
|
|
550
552
|
#--}}}
|
551
553
|
end
|
552
554
|
|
555
|
+
def Lockfile path, *a, &b
|
556
|
+
#--{{{
|
557
|
+
Lockfile.new(path, *a, &b)
|
558
|
+
#--}}}
|
559
|
+
end
|
560
|
+
|
553
561
|
$__lockfile__ == __FILE__
|
554
562
|
end
|
data/lib/lockfile.rb
CHANGED
@@ -5,7 +5,7 @@ unless defined? $__lockfile__
|
|
5
5
|
|
6
6
|
class Lockfile
|
7
7
|
#--{{{
|
8
|
-
VERSION = '1.4.
|
8
|
+
VERSION = '1.4.1'
|
9
9
|
|
10
10
|
class LockError < StandardError; end
|
11
11
|
class StolenLockError < LockError; end
|
@@ -171,21 +171,20 @@ unless defined? $__lockfile__
|
|
171
171
|
@path = path
|
172
172
|
@opts = opts
|
173
173
|
|
174
|
-
@retries = getopt
|
175
|
-
@max_age = getopt
|
176
|
-
@sleep_inc = getopt
|
177
|
-
@min_sleep = getopt
|
178
|
-
@max_sleep = getopt
|
179
|
-
@suspend = getopt
|
180
|
-
@timeout = getopt
|
181
|
-
@refresh = getopt
|
182
|
-
@dont_clean = getopt
|
183
|
-
@poll_retries = getopt
|
184
|
-
@poll_max_sleep = getopt
|
185
|
-
@dont_sweep = getopt
|
186
|
-
@dont_use_lock_id = getopt
|
187
|
-
|
188
|
-
@debug = getopt('debug') || @klass.debug
|
174
|
+
@retries = getopt 'retries' , @klass.retries
|
175
|
+
@max_age = getopt 'max_age' , @klass.max_age
|
176
|
+
@sleep_inc = getopt 'sleep_inc' , @klass.sleep_inc
|
177
|
+
@min_sleep = getopt 'min_sleep' , @klass.min_sleep
|
178
|
+
@max_sleep = getopt 'max_sleep' , @klass.max_sleep
|
179
|
+
@suspend = getopt 'suspend' , @klass.suspend
|
180
|
+
@timeout = getopt 'timeout' , @klass.timeout
|
181
|
+
@refresh = getopt 'refresh' , @klass.refresh
|
182
|
+
@dont_clean = getopt 'dont_clean' , @klass.dont_clean
|
183
|
+
@poll_retries = getopt 'poll_retries' , @klass.poll_retries
|
184
|
+
@poll_max_sleep = getopt 'poll_max_sleep' , @klass.poll_max_sleep
|
185
|
+
@dont_sweep = getopt 'dont_sweep' , @klass.dont_sweep
|
186
|
+
@dont_use_lock_id = getopt 'dont_use_lock_id' , @klass.dont_use_lock_id
|
187
|
+
@debug = getopt 'debug' , @klass.debug
|
189
188
|
|
190
189
|
@sleep_cycle = SleepCycle::new @min_sleep, @max_sleep, @sleep_inc
|
191
190
|
|
@@ -508,9 +507,12 @@ unless defined? $__lockfile__
|
|
508
507
|
FileUtils.touch path
|
509
508
|
#--}}}
|
510
509
|
end
|
511
|
-
def getopt key
|
510
|
+
def getopt key, default = nil
|
512
511
|
#--{{{
|
513
|
-
|
512
|
+
[ key, key.to_s, key.to_s.intern ].each do |k|
|
513
|
+
return @opts[k] if @opts.has_key?(k)
|
514
|
+
end
|
515
|
+
return default
|
514
516
|
#--}}}
|
515
517
|
end
|
516
518
|
def to_str
|
@@ -550,5 +552,11 @@ unless defined? $__lockfile__
|
|
550
552
|
#--}}}
|
551
553
|
end
|
552
554
|
|
555
|
+
def Lockfile path, *a, &b
|
556
|
+
#--{{{
|
557
|
+
Lockfile.new(path, *a, &b)
|
558
|
+
#--}}}
|
559
|
+
end
|
560
|
+
|
553
561
|
$__lockfile__ == __FILE__
|
554
562
|
end
|
data/rlock
ADDED
data/samples/a.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift '../lib'
|
3
|
+
#
|
4
|
+
# puts this script in an nfs located directory and run from a couple of nodes at
|
5
|
+
# once. the list should appear ordered from either host - note that times may
|
6
|
+
# not be ordered depending on the system clocks
|
7
|
+
#
|
8
|
+
|
9
|
+
#
|
10
|
+
# builtin
|
11
|
+
#
|
12
|
+
require 'socket'
|
13
|
+
require 'pstore'
|
14
|
+
#
|
15
|
+
# do what we can to invalidate any nfs caching
|
16
|
+
#
|
17
|
+
def timestamp time = Time.now
|
18
|
+
#{{{
|
19
|
+
usec = "#{ time.usec }"
|
20
|
+
usec << ('0' * (6 - usec.size)) if usec.size < 6
|
21
|
+
time.strftime('%Y-%m-%d %H:%M:%S.') << usec
|
22
|
+
#}}}
|
23
|
+
end
|
24
|
+
def hostname
|
25
|
+
#{{{
|
26
|
+
@__hostname__ ||= Socket::gethostname
|
27
|
+
#}}}
|
28
|
+
end
|
29
|
+
def tmpnam dir = Dir.tmpdir, seed = File.basename($0)
|
30
|
+
#{{{
|
31
|
+
pid = Process.pid
|
32
|
+
path = "%s_%s_%s_%s_%d" %
|
33
|
+
[hostname, seed, pid, timestamp.gsub(/\s+/o,'_'), rand(101010)]
|
34
|
+
File.join(dir, path)
|
35
|
+
#}}}
|
36
|
+
end
|
37
|
+
def uncache file
|
38
|
+
#{{{
|
39
|
+
refresh = nil
|
40
|
+
begin
|
41
|
+
is_a_file = File === file
|
42
|
+
path = (is_a_file ? file.path : file.to_s)
|
43
|
+
stat = (is_a_file ? file.stat : File.stat(file.to_s))
|
44
|
+
refresh = tmpnam(File.dirname(path))
|
45
|
+
File.link path, refresh rescue File.symlink path, refresh
|
46
|
+
File.chmod stat.mode, path
|
47
|
+
File.utime stat.atime, stat.mtime, path
|
48
|
+
ensure
|
49
|
+
begin
|
50
|
+
File.unlink refresh if refresh
|
51
|
+
rescue Errno::ENOENT
|
52
|
+
end
|
53
|
+
end
|
54
|
+
#}}}
|
55
|
+
end
|
56
|
+
#
|
57
|
+
# raa - http://raa.ruby-lang.org/project/lockfile/
|
58
|
+
#
|
59
|
+
require 'lockfile'
|
60
|
+
pstore = PStore.new 'test.db'
|
61
|
+
timeout = 60
|
62
|
+
max_age = 8
|
63
|
+
refresh = 2
|
64
|
+
debug = false
|
65
|
+
lockfile = Lockfile.new 'test.lock',
|
66
|
+
:timeout => timeout,
|
67
|
+
:max_age => max_age,
|
68
|
+
:refresh => refresh,
|
69
|
+
:debug => debug
|
70
|
+
#
|
71
|
+
# flock throws ENOLCK on nfs file systems in newer linux kernels
|
72
|
+
# plus we want to show that lockfile alone can do the locking
|
73
|
+
#
|
74
|
+
class File
|
75
|
+
def flock(*args,&block);true;end
|
76
|
+
end
|
77
|
+
#
|
78
|
+
# if locking does not work this loop will blow up (Marshal load error) or appear
|
79
|
+
# un-ordered. actually it will eventually blow up due to nfs caching - but that
|
80
|
+
# is not the fault of the lockfile class! for the most part it is a simply demo
|
81
|
+
# of locking. the file will never become corrupt, it just will be unreadable at
|
82
|
+
# times due to kernel caching.
|
83
|
+
#
|
84
|
+
loop do
|
85
|
+
lockfile.lock do
|
86
|
+
uncache pstore.path
|
87
|
+
pstore.transaction do
|
88
|
+
#
|
89
|
+
# get/update list
|
90
|
+
#
|
91
|
+
pstore[:list] = [] unless pstore.root? :list
|
92
|
+
list = pstore[:list]
|
93
|
+
tuple = [list.size, hostname, Time.now.to_f]
|
94
|
+
list << tuple
|
95
|
+
#
|
96
|
+
# show last 16 elements
|
97
|
+
#
|
98
|
+
puts '---'
|
99
|
+
list[-([list.size, 16].min)..-1].each{|tuple| p tuple}
|
100
|
+
puts '---'
|
101
|
+
#
|
102
|
+
# keep it a reasonable size
|
103
|
+
#
|
104
|
+
list.shift while list.size > 1024
|
105
|
+
#
|
106
|
+
# write back updates
|
107
|
+
#
|
108
|
+
pstore[:list] = list
|
109
|
+
end
|
110
|
+
end
|
111
|
+
sleep 1
|
112
|
+
end
|
data/samples/lock
ADDED
data/samples/lock.sh
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
export RUBYLIB="../lib:./lib"
|
4
|
+
|
5
|
+
export PATH="../bin:./bin:$PATH"
|
6
|
+
|
7
|
+
#export LOCKFILE_DEBUG=1
|
8
|
+
|
9
|
+
(r=`ruby -e 'p(rand(2))'`; sleep $r; rlock ./lockfile 'sleep 1; printf "\n$$ aquired lock @ `date`\n\n"') &
|
10
|
+
(r=`ruby -e 'p(rand(2))'`; sleep $r; rlock ./lockfile 'sleep 1; printf "\n$$ aquired lock @ `date`\n\n"') &
|
11
|
+
(r=`ruby -e 'p(rand(2))'`; sleep $r; rlock ./lockfile 'sleep 1; printf "\n$$ aquired lock @ `date`\n\n"') &
|
12
|
+
|
13
|
+
wait
|
data/samples/lockfile
ADDED
data/samples/nfsstore.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
#
|
2
|
+
# How to use:
|
3
|
+
#
|
4
|
+
# db = NFSStore.new("/tmp/foo")
|
5
|
+
# db.transaction do
|
6
|
+
# p db.roots
|
7
|
+
# ary = db["root"] = [1,2,3,4]
|
8
|
+
# ary[0] = [1,1.5]
|
9
|
+
# end
|
10
|
+
|
11
|
+
# db.transaction do
|
12
|
+
# p db["root"]
|
13
|
+
# end
|
14
|
+
|
15
|
+
require "ftools"
|
16
|
+
require "digest/md5"
|
17
|
+
require "socket"
|
18
|
+
|
19
|
+
require 'lockfile'
|
20
|
+
|
21
|
+
class NFSStore
|
22
|
+
HOSTNAME = Socket::gethostname
|
23
|
+
class Error < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(file)
|
27
|
+
dir = File::dirname(file)
|
28
|
+
unless File::directory? dir
|
29
|
+
raise NFSStore::Error, format("directory %s does not exist", dir)
|
30
|
+
end
|
31
|
+
if File::exist? file and not File::readable? file
|
32
|
+
raise NFSStore::Error, format("file %s not readable", file)
|
33
|
+
end
|
34
|
+
@transaction = false
|
35
|
+
@filename = file
|
36
|
+
@lockfile = Lockfile.new "#{ @filename }.lock",:max_age=>64,:refresh=>8
|
37
|
+
@abort = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def in_transaction
|
41
|
+
raise NFSStore::Error, "not in transaction" unless @transaction
|
42
|
+
end
|
43
|
+
private :in_transaction
|
44
|
+
|
45
|
+
def [](name)
|
46
|
+
in_transaction
|
47
|
+
@table[name]
|
48
|
+
end
|
49
|
+
def fetch(name, default=NFSStore::Error)
|
50
|
+
unless @table.key? name
|
51
|
+
if default==NFSStore::Error
|
52
|
+
raise NFSStore::Error, format("undefined root name `%s'", name)
|
53
|
+
else
|
54
|
+
default
|
55
|
+
end
|
56
|
+
end
|
57
|
+
self[name]
|
58
|
+
end
|
59
|
+
def []=(name, value)
|
60
|
+
in_transaction
|
61
|
+
@table[name] = value
|
62
|
+
end
|
63
|
+
def delete(name)
|
64
|
+
in_transaction
|
65
|
+
@table.delete name
|
66
|
+
end
|
67
|
+
def roots
|
68
|
+
in_transaction
|
69
|
+
@table.keys
|
70
|
+
end
|
71
|
+
def root?(name)
|
72
|
+
in_transaction
|
73
|
+
@table.key? name
|
74
|
+
end
|
75
|
+
def path
|
76
|
+
@filename
|
77
|
+
end
|
78
|
+
def commit
|
79
|
+
in_transaction
|
80
|
+
@abort = false
|
81
|
+
throw :pstore_abort_transaction
|
82
|
+
end
|
83
|
+
def abort
|
84
|
+
in_transaction
|
85
|
+
@abort = true
|
86
|
+
throw :pstore_abort_transaction
|
87
|
+
end
|
88
|
+
|
89
|
+
# do what we can to invalidate any nfs caching
|
90
|
+
def uncache file
|
91
|
+
begin
|
92
|
+
stat = file.stat
|
93
|
+
path = file.path
|
94
|
+
refresh = "%s.%s.%s.%s" % [path, HOSTNAME, $$, Time.now.to_i % 1024]
|
95
|
+
File.link path, refresh
|
96
|
+
file.chmod stat.mode
|
97
|
+
File.utime stat.atime, stat.mtime, path
|
98
|
+
rescue Exception => e
|
99
|
+
warn e
|
100
|
+
ensure
|
101
|
+
begin
|
102
|
+
File.unlink refresh if File.exist? refresh
|
103
|
+
rescue Exception => e
|
104
|
+
warn e
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def transaction(read_only=false)
|
111
|
+
raise NFSStore::Error, "nested transaction" if @transaction
|
112
|
+
file = nil
|
113
|
+
value = nil
|
114
|
+
|
115
|
+
@lockfile.lock do
|
116
|
+
begin
|
117
|
+
@transaction = true
|
118
|
+
backup = @filename+"~"
|
119
|
+
begin
|
120
|
+
file = File::open(@filename, read_only ? "rb" : "rb+")
|
121
|
+
orig = true
|
122
|
+
rescue Errno::ENOENT
|
123
|
+
raise if read_only
|
124
|
+
file = File::open(@filename, "wb+")
|
125
|
+
end
|
126
|
+
#file.flock(read_only ? File::LOCK_SH : File::LOCK_EX)
|
127
|
+
uncache file
|
128
|
+
file.rewind
|
129
|
+
if read_only
|
130
|
+
@table = Marshal::load(file)
|
131
|
+
elsif orig and (content = file.read) != ""
|
132
|
+
@table = Marshal::load(content)
|
133
|
+
size = content.size
|
134
|
+
md5 = Digest::MD5.digest(content)
|
135
|
+
content = nil # unreference huge data
|
136
|
+
else
|
137
|
+
@table = {}
|
138
|
+
end
|
139
|
+
begin
|
140
|
+
catch(:pstore_abort_transaction) do
|
141
|
+
value = yield(self)
|
142
|
+
end
|
143
|
+
rescue Exception
|
144
|
+
@abort = true
|
145
|
+
raise
|
146
|
+
ensure
|
147
|
+
if !read_only and !@abort
|
148
|
+
file.rewind
|
149
|
+
content = Marshal::dump(@table)
|
150
|
+
if !md5 || size != content.size || md5 != Digest::MD5.digest(content)
|
151
|
+
File::copy @filename, backup
|
152
|
+
begin
|
153
|
+
file.write(content)
|
154
|
+
file.truncate(file.pos)
|
155
|
+
content = nil # unreference huge data
|
156
|
+
rescue
|
157
|
+
File::rename backup, @filename if File::exist?(backup)
|
158
|
+
raise
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
if @abort and !orig
|
163
|
+
File.unlink(@filename)
|
164
|
+
end
|
165
|
+
@abort = false
|
166
|
+
end
|
167
|
+
ensure
|
168
|
+
@table = nil
|
169
|
+
@transaction = false
|
170
|
+
file.close if file
|
171
|
+
end
|
172
|
+
end
|
173
|
+
value
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
if __FILE__ == $0
|
186
|
+
db = NFSStore.new("/tmp/foo")
|
187
|
+
db.transaction do
|
188
|
+
p db.roots
|
189
|
+
ary = db["root"] = [1,2,3,4]
|
190
|
+
ary[1] = [1,1.5]
|
191
|
+
end
|
192
|
+
|
193
|
+
1000.times do
|
194
|
+
db.transaction do
|
195
|
+
db["root"][0] += 1
|
196
|
+
p db["root"][0]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
db.transaction(true) do
|
201
|
+
p db["root"]
|
202
|
+
end
|
203
|
+
end
|
data/samples/out
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
lock_id <
|
2
|
+
{"pid"=>"15681", "time"=>"2004-11-16 17:08:42.931418", "host"=>"jib.ngdc.noaa.gov", "ppid"=>"15674"}
|
3
|
+
>
|
4
|
+
attempting to lock <./lockfile>...
|
5
|
+
polling attempt <0>...
|
6
|
+
aquired lock <./lockfile>
|
7
|
+
touched <./lockfile> @ <1100650122.93628>
|
8
|
+
loaded <
|
9
|
+
{"pid"=>"15681", "time"=>"2004-11-16 17:08:42.931418", "host"=>"jib.ngdc.noaa.gov", "ppid"=>"15674"}
|
10
|
+
>
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
15682 aquired lock @ Tue Nov 16 17:08:43 MST 2004
|
15
|
+
|
16
|
+
|
17
|
+
lock_id <
|
18
|
+
{"pid"=>"15685", "time"=>"2004-11-16 17:08:43.962744", "host"=>"jib.ngdc.noaa.gov", "ppid"=>"15676"}
|
19
|
+
>
|
20
|
+
attempting to lock <./lockfile>...
|
21
|
+
polling attempt <0>...
|
22
|
+
poll sleep <0.08>...
|
23
|
+
lock_id <
|
24
|
+
{"pid"=>"15684", "time"=>"2004-11-16 17:08:43.964636", "host"=>"jib.ngdc.noaa.gov", "ppid"=>"15672"}
|
25
|
+
>
|
26
|
+
attempting to lock <./lockfile>...
|
27
|
+
polling attempt <0>...
|
28
|
+
poll sleep <0.08>...
|
29
|
+
polling attempt <1>...polling attempt <1>...
|
30
|
+
|
31
|
+
aquired lock <./lockfile>
|
32
|
+
poll sleep <0.08>...
|
33
|
+
touched <./lockfile> @ <1100650124.04751>
|
34
|
+
loaded <
|
35
|
+
{"pid"=>"15685", "time"=>"2004-11-16 17:08:43.962744", "host"=>"jib.ngdc.noaa.gov", "ppid"=>"15676"}
|
36
|
+
>
|
37
|
+
polling attempt <2>...
|
38
|
+
poll sleep <0.08>...
|
39
|
+
polling attempt <3>...
|
40
|
+
poll sleep <0.08>...
|
41
|
+
polling attempt <4>...
|
42
|
+
poll sleep <0.08>...
|
43
|
+
polling attempt <5>...
|
44
|
+
poll sleep <0.08>...
|
45
|
+
polling attempt <6>...
|
46
|
+
poll sleep <0.08>...
|
47
|
+
polling attempt <7>...
|
48
|
+
poll sleep <0.08>...
|
49
|
+
polling attempt <8>...
|
50
|
+
poll sleep <0.08>...
|
51
|
+
polling attempt <9>...
|
52
|
+
poll sleep <0.08>...
|
53
|
+
polling attempt <10>...
|
54
|
+
poll sleep <0.08>...
|
55
|
+
polling attempt <11>...
|
56
|
+
poll sleep <0.08>...
|
57
|
+
polling attempt <12>...
|
58
|
+
poll sleep <0.08>...
|
59
|
+
polling attempt <13>...
|
60
|
+
poll sleep <0.08>...
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
15690 aquired lock @ Tue Nov 16 17:08:45 MST 2004
|
65
|
+
|
66
|
+
|
67
|
+
polling attempt <14>...
|
68
|
+
poll sleep <0.08>...
|
69
|
+
polling attempt <15>...
|
70
|
+
aquired lock <./lockfile>
|
71
|
+
touched <./lockfile> @ <1100650125.16655>
|
72
|
+
loaded <
|
73
|
+
{"pid"=>"15684", "time"=>"2004-11-16 17:08:43.964636", "host"=>"jib.ngdc.noaa.gov", "ppid"=>"15672"}
|
74
|
+
>
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
15694 aquired lock @ Tue Nov 16 17:08:46 MST 2004
|
79
|
+
|
80
|
+
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: lockfile
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.4.
|
7
|
-
date:
|
6
|
+
version: 1.4.1
|
7
|
+
date: 2007-01-04 00:00:00.000000 -07:00
|
8
8
|
summary: lockfile
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,14 +29,31 @@ cert_chain:
|
|
29
29
|
authors:
|
30
30
|
- Ara T. Howard
|
31
31
|
files:
|
32
|
+
- install.rb
|
33
|
+
- README
|
34
|
+
- rlock
|
35
|
+
- lib
|
36
|
+
- bin
|
37
|
+
- samples
|
38
|
+
- doc
|
39
|
+
- gemspec.rb
|
32
40
|
- lib/lockfile.rb
|
33
|
-
- lib/lockfile-1.4.
|
41
|
+
- lib/lockfile-1.4.1.rb
|
34
42
|
- bin/rlock
|
35
|
-
- bin/rlock-1.
|
43
|
+
- bin/rlock-1.4.1
|
44
|
+
- samples/a.rb
|
45
|
+
- samples/nfsstore.rb
|
46
|
+
- samples/out
|
47
|
+
- samples/lock.sh
|
48
|
+
- samples/lock
|
49
|
+
- samples/lockfile
|
50
|
+
- doc/rlock.help
|
36
51
|
test_files: []
|
37
52
|
rdoc_options: []
|
38
53
|
extra_rdoc_files: []
|
39
|
-
executables:
|
54
|
+
executables:
|
55
|
+
- rlock
|
56
|
+
- rlock-1.4.1
|
40
57
|
extensions: []
|
41
58
|
requirements: []
|
42
59
|
dependencies: []
|