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/bin/rlock +360 -0
- data/doc/rlock.help +95 -0
- data/lib/lockfile.rb +536 -0
- data/rakefile +228 -0
- data/readme.erb +227 -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 +77 -0
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
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
|
+
|