lockfile 1.4.0 → 1.4.1

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/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: []