thoughtafter-lockfile 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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 ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thoughtafter-lockfile
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
+ prerelease: false
6
+ segments:
7
+ - 2
8
+ - 0
9
+ - 0
10
+ version: 2.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Ara T. Howard
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-01 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description:
23
+ email: ara.t.howard@gmail.com
24
+ executables:
25
+ - rlock
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - bin/rlock
32
+ - doc/rlock.help
33
+ - lib/lockfile.rb
34
+ - rakefile
35
+ - readme.erb
36
+ - samples/a.rb
37
+ - samples/lock
38
+ - samples/lock.sh
39
+ - samples/lockfile
40
+ - samples/nfsstore.rb
41
+ - samples/out
42
+ has_rdoc: true
43
+ homepage: http://github.com/ahoward/lockfile/tree/master
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ requirements: []
70
+
71
+ rubyforge_project: codeforpeople
72
+ rubygems_version: 1.3.7
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: lockfile
76
+ test_files: []
77
+