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 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.3.0.rb'
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
- @op.def_option(*option){|v| self.send(set, (v or true))}
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.3.0.rb'
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
- @op.def_option(*option){|v| self.send(set, (v or true))}
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.0'
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('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
-
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
- @opts[key] || @opts[key.to_s] || @opts[key.to_s.intern]
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.0'
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('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
-
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
- @opts[key] || @opts[key.to_s] || @opts[key.to_s.intern]
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
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ RUBYLIB=./lib ./bin/rlock "$@"
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
@@ -0,0 +1,4 @@
1
+ host: jib.ngdc.noaa.gov
2
+ pid: 18176
3
+ ppid: 5245
4
+ time: 2004-11-18 14:40:43.218731
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
@@ -0,0 +1,4 @@
1
+ host: jib.ngdc.noaa.gov
2
+ pid: 18193
3
+ ppid: 5245
4
+ time: 2004-11-18 14:41:59.320552
@@ -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.0
7
- date: 2005-11-10 00:00:00.000000 -07:00
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.0.rb
41
+ - lib/lockfile-1.4.1.rb
34
42
  - bin/rlock
35
- - bin/rlock-1.3.0
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: []