lock_method 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -4
- data/lib/lock_method/default_storage_client.rb +23 -6
- data/lib/lock_method/lock.rb +28 -42
- data/lib/lock_method/version.rb +1 -1
- data/lib/lock_method.rb +2 -2
- data/test/helper.rb +8 -3
- data/test/shared_tests.rb +30 -11
- metadata +3 -31
data/README.rdoc
CHANGED
@@ -32,11 +32,9 @@ Just in case, you can clear them
|
|
32
32
|
|
33
33
|
In production use at {carbon.brighterplanet.com}[http://carbon.brighterplanet.com], the Brighter Planet emission estimate web service.
|
34
34
|
|
35
|
-
==
|
35
|
+
== Pays attention to method arguments
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
Maybe future versions will support this.
|
37
|
+
If you lock Foo.bar(*args), calling Foo.bar(:baz) will not lock out Foo.bar(:zoo).
|
40
38
|
|
41
39
|
== Defining #hash
|
42
40
|
|
@@ -4,18 +4,37 @@ require 'fileutils'
|
|
4
4
|
require 'thread'
|
5
5
|
module LockMethod
|
6
6
|
class DefaultStorageClient #:nodoc: all
|
7
|
+
|
7
8
|
include ::Singleton
|
9
|
+
|
10
|
+
class Entry
|
11
|
+
attr_reader :created_at
|
12
|
+
attr_reader :ttl
|
13
|
+
attr_reader :v
|
14
|
+
def initialize(attrs = {})
|
15
|
+
attrs.each do |k, v|
|
16
|
+
instance_variable_set "@#{k}", v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def expired?
|
20
|
+
ttl.to_i > 0 and (::Time.now.to_f - created_at.to_f) > ttl.to_i
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
8
24
|
def get(k)
|
9
|
-
|
10
|
-
|
25
|
+
if ::File.exist?(path(k)) and entry = ::Marshal.load(::File.read(path(k))) and not entry.expired?
|
26
|
+
entry.v
|
27
|
+
end
|
11
28
|
rescue ::Errno::ENOENT
|
12
29
|
end
|
13
30
|
|
14
31
|
def set(k, v, ttl)
|
32
|
+
entry = Entry.new :created_at => ::Time.now.to_f, :ttl => ttl, :v => v
|
15
33
|
semaphore.synchronize do
|
34
|
+
::FileUtils.mkdir_p dir unless ::File.directory? dir
|
16
35
|
::File.open(path(k), ::File::RDWR|::File::CREAT) do |f|
|
17
36
|
f.flock ::File::LOCK_EX
|
18
|
-
f.write ::Marshal.dump(
|
37
|
+
f.write ::Marshal.dump(entry)
|
19
38
|
end
|
20
39
|
end
|
21
40
|
end
|
@@ -39,9 +58,7 @@ module LockMethod
|
|
39
58
|
end
|
40
59
|
|
41
60
|
def dir
|
42
|
-
|
43
|
-
::FileUtils.mkdir_p dir unless ::File.directory? dir
|
44
|
-
dir
|
61
|
+
::File.expand_path ::File.join(::Dir.tmpdir, 'lock_method')
|
45
62
|
end
|
46
63
|
end
|
47
64
|
end
|
data/lib/lock_method/lock.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
+
require 'digest/md5'
|
1
2
|
module LockMethod
|
2
3
|
class Lock #:nodoc: all
|
3
4
|
class << self
|
4
5
|
def find(cache_key)
|
5
|
-
|
6
|
-
new hsh
|
7
|
-
end
|
6
|
+
Config.instance.storage.get cache_key
|
8
7
|
end
|
9
8
|
def klass_name(obj)
|
10
9
|
(obj.is_a?(::Class) or obj.is_a?(::Module)) ? obj.to_s : obj.class.to_s
|
@@ -27,15 +26,14 @@ module LockMethod
|
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
30
|
-
def initialize(
|
31
|
-
|
29
|
+
def initialize(attrs = {})
|
30
|
+
attrs.each do |k, v|
|
32
31
|
instance_variable_set "@#{k}", v
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
35
|
attr_reader :obj
|
37
36
|
attr_reader :method_id
|
38
|
-
attr_reader :original_method_id
|
39
37
|
attr_reader :args
|
40
38
|
|
41
39
|
def method_signature
|
@@ -54,71 +52,59 @@ module LockMethod
|
|
54
52
|
@thread_object_id ||= ::Thread.current.object_id
|
55
53
|
end
|
56
54
|
|
57
|
-
def
|
58
|
-
@
|
55
|
+
def obj_hash
|
56
|
+
@obj_hash ||= obj.respond_to?(:method_lock_hash) ? obj.method_lock_hash : obj.hash
|
59
57
|
end
|
60
|
-
|
58
|
+
|
59
|
+
def args_digest
|
60
|
+
@args_digest ||= args.to_a.empty? ? 'empty' : ::Digest::MD5.hexdigest(args.join)
|
61
|
+
end
|
62
|
+
|
61
63
|
def delete
|
62
64
|
Config.instance.storage.delete cache_key
|
63
65
|
end
|
64
66
|
|
65
67
|
def save
|
66
|
-
|
67
|
-
self.pid
|
68
|
-
self.thread_object_id
|
69
|
-
self.expiry
|
70
|
-
# --
|
71
|
-
Config.instance.storage.set cache_key, to_hash, ttl
|
72
|
-
end
|
73
|
-
|
74
|
-
def to_hash
|
75
|
-
instance_variables.inject({}) do |memo, ivar_name|
|
76
|
-
memo[ivar_name.to_s.sub('@', '')] = instance_variable_get ivar_name
|
77
|
-
memo
|
78
|
-
end
|
68
|
+
Config.instance.storage.set cache_key, self, ttl
|
79
69
|
end
|
80
70
|
|
81
71
|
def locked?
|
82
|
-
if
|
83
|
-
|
72
|
+
if other_lock = Lock.find(cache_key)
|
73
|
+
other_lock.process_and_thread_still_alive?
|
84
74
|
end
|
85
75
|
end
|
86
76
|
|
87
77
|
def cache_key
|
88
78
|
if obj.is_a?(::Class) or obj.is_a?(::Module)
|
89
|
-
[ 'LockMethod', 'Lock', method_signature ].join ','
|
79
|
+
[ 'LockMethod', 'Lock', method_signature, args_digest ].join ','
|
90
80
|
else
|
91
|
-
[ 'LockMethod', 'Lock', method_signature, obj_hash ].join ','
|
81
|
+
[ 'LockMethod', 'Lock', method_signature, obj_hash, args_digest ].join ','
|
92
82
|
end
|
93
83
|
end
|
94
|
-
|
95
|
-
def
|
96
|
-
@obj_hash ||= obj.respond_to?(:method_lock_hash) ? obj.method_lock_hash : obj.hash
|
97
|
-
end
|
98
|
-
|
99
|
-
def in_force?
|
100
|
-
not expired? and process_and_thread_still_exist?
|
101
|
-
end
|
102
|
-
|
103
|
-
def expired?
|
104
|
-
expiry.to_f < ::Time.now.to_f
|
105
|
-
end
|
106
|
-
|
107
|
-
def process_and_thread_still_exist?
|
84
|
+
|
85
|
+
def process_and_thread_still_alive?
|
108
86
|
if pid == ::Process.pid
|
109
87
|
Lock.thread_alive? thread_object_id
|
110
88
|
else
|
111
89
|
Lock.process_alive? pid
|
112
90
|
end
|
113
91
|
end
|
92
|
+
|
93
|
+
def marshal_dump
|
94
|
+
[ pid, thread_object_id ]
|
95
|
+
end
|
96
|
+
|
97
|
+
def marshal_load(source)
|
98
|
+
@pid, @thread_object_id = source
|
99
|
+
end
|
114
100
|
|
115
|
-
def
|
101
|
+
def call_and_lock(*original_method_id_and_args)
|
116
102
|
if locked?
|
117
103
|
raise Locked
|
118
104
|
else
|
119
105
|
begin
|
120
106
|
save
|
121
|
-
obj.send
|
107
|
+
obj.send *original_method_id_and_args
|
122
108
|
ensure
|
123
109
|
delete
|
124
110
|
end
|
data/lib/lock_method/version.rb
CHANGED
data/lib/lock_method.rb
CHANGED
@@ -46,8 +46,8 @@ module LockMethod
|
|
46
46
|
original_method_id = "_unlocked_#{method_id}"
|
47
47
|
alias_method original_method_id, method_id
|
48
48
|
define_method method_id do |*args|
|
49
|
-
lock = ::LockMethod::Lock.new :obj => self, :method_id => method_id, :
|
50
|
-
lock.
|
49
|
+
lock = ::LockMethod::Lock.new :obj => self, :method_id => method_id, :args => args, :ttl => ttl
|
50
|
+
lock.call_and_lock original_method_id, *args
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
data/test/helper.rb
CHANGED
@@ -16,7 +16,7 @@ class Blog1
|
|
16
16
|
@url = url
|
17
17
|
end
|
18
18
|
def get_latest_entries
|
19
|
-
sleep
|
19
|
+
sleep 2
|
20
20
|
["hello from #{name}"]
|
21
21
|
end
|
22
22
|
lock_method :get_latest_entries
|
@@ -42,7 +42,7 @@ end
|
|
42
42
|
class Blog2
|
43
43
|
class << self
|
44
44
|
def get_latest_entries
|
45
|
-
sleep
|
45
|
+
sleep 2
|
46
46
|
'danke schoen'
|
47
47
|
end
|
48
48
|
lock_method :get_latest_entries
|
@@ -51,12 +51,17 @@ class Blog2
|
|
51
51
|
["voo vaa #{name}"]
|
52
52
|
end
|
53
53
|
lock_method :get_latest_entries2, 5 # second
|
54
|
+
def work_really_hard_on(target)
|
55
|
+
sleep 2
|
56
|
+
target.to_s
|
57
|
+
end
|
58
|
+
lock_method :work_really_hard_on
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
57
62
|
module BlogM
|
58
63
|
def self.get_latest_entries
|
59
|
-
sleep
|
64
|
+
sleep 2
|
60
65
|
'danke schoen'
|
61
66
|
end
|
62
67
|
def self.get_latest_entries2
|
data/test/shared_tests.rb
CHANGED
@@ -6,11 +6,11 @@ def new_instance_of_another_blog
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module SharedTests
|
9
|
-
def
|
9
|
+
def test_01_locked_method_return_value
|
10
10
|
assert_equal ["hello from my_blog"], new_instance_of_my_blog.get_latest_entries
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def test_02_locked_by_normally_terminating_process
|
14
14
|
pid = Kernel.fork { Blog2.get_latest_entries }
|
15
15
|
|
16
16
|
# give it a bit of time to lock
|
@@ -29,7 +29,7 @@ module SharedTests
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
def test_03_module_method_locked_by_normally_terminating_process
|
33
33
|
pid = Kernel.fork { BlogM.get_latest_entries }
|
34
34
|
|
35
35
|
# give it a bit of time to lock
|
@@ -48,7 +48,7 @@ module SharedTests
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def
|
51
|
+
def test_04_locked_by_SIGKILLed_process
|
52
52
|
pid = Kernel.fork { Blog2.get_latest_entries }
|
53
53
|
|
54
54
|
# give it a bit of time to lock
|
@@ -70,7 +70,7 @@ module SharedTests
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
def
|
73
|
+
def test_05_locked_by_killed_thread
|
74
74
|
blocker = Thread.new { Blog2.get_latest_entries }
|
75
75
|
|
76
76
|
# give it a bit of time to lock
|
@@ -90,7 +90,7 @@ module SharedTests
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
def
|
93
|
+
def test_06_locked_by_normally_finishing_thread
|
94
94
|
blocker = Thread.new { Blog2.get_latest_entries }
|
95
95
|
|
96
96
|
# give it a bit of time to lock
|
@@ -110,7 +110,7 @@ module SharedTests
|
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
113
|
-
def
|
113
|
+
def test_07_lock_instance_method
|
114
114
|
pid = Kernel.fork { new_instance_of_my_blog.get_latest_entries }
|
115
115
|
|
116
116
|
# give it a bit of time to lock
|
@@ -130,7 +130,7 @@ module SharedTests
|
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
-
def
|
133
|
+
def test_08_instance_method_lock_is_unique_to_instance
|
134
134
|
pid = Kernel.fork { new_instance_of_my_blog.get_latest_entries }
|
135
135
|
|
136
136
|
# give it a bit of time to lock
|
@@ -144,7 +144,7 @@ module SharedTests
|
|
144
144
|
Process.wait pid
|
145
145
|
end
|
146
146
|
|
147
|
-
def
|
147
|
+
def test_09_clear_instance_method_lock
|
148
148
|
pid = Kernel.fork { new_instance_of_my_blog.get_latest_entries }
|
149
149
|
|
150
150
|
# give it a bit of time to lock
|
@@ -164,7 +164,7 @@ module SharedTests
|
|
164
164
|
Process.wait pid
|
165
165
|
end
|
166
166
|
|
167
|
-
def
|
167
|
+
def test_10_clear_class_method_lock
|
168
168
|
pid = Kernel.fork { Blog2.get_latest_entries }
|
169
169
|
|
170
170
|
# give it a bit of time to lock
|
@@ -184,7 +184,7 @@ module SharedTests
|
|
184
184
|
Process.wait pid
|
185
185
|
end
|
186
186
|
|
187
|
-
def
|
187
|
+
def test_11_expiring_lock
|
188
188
|
pid = Kernel.fork { Blog2.get_latest_entries2 }
|
189
189
|
|
190
190
|
# give it a bit of time to lock
|
@@ -209,4 +209,23 @@ module SharedTests
|
|
209
209
|
|
210
210
|
Process.wait pid
|
211
211
|
end
|
212
|
+
|
213
|
+
def test_12_locked_according_to_method_arguments
|
214
|
+
pid = Kernel.fork { Blog2.work_really_hard_on :foo }
|
215
|
+
|
216
|
+
# give it a bit of time to lock
|
217
|
+
sleep 1
|
218
|
+
|
219
|
+
# the blocker won't have finished
|
220
|
+
assert_raises(LockMethod::Locked) do
|
221
|
+
Blog2.work_really_hard_on :foo
|
222
|
+
end
|
223
|
+
|
224
|
+
# let the blocker finish
|
225
|
+
Process.wait pid
|
226
|
+
|
227
|
+
assert_nothing_raised do
|
228
|
+
Blog2.work_really_hard_on :foo
|
229
|
+
end
|
230
|
+
end
|
212
231
|
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lock_method
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 29
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
- 3
|
10
|
-
version: 0.1.3
|
5
|
+
version: 0.2.0
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Seamus Abshere
|
@@ -15,7 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-03-
|
13
|
+
date: 2011-03-15 00:00:00 -05:00
|
19
14
|
default_executable:
|
20
15
|
dependencies:
|
21
16
|
- !ruby/object:Gem::Dependency
|
@@ -26,11 +21,6 @@ dependencies:
|
|
26
21
|
requirements:
|
27
22
|
- - ">="
|
28
23
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 21
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
- 2
|
33
|
-
- 1
|
34
24
|
version: 0.2.1
|
35
25
|
type: :runtime
|
36
26
|
version_requirements: *id001
|
@@ -42,9 +32,6 @@ dependencies:
|
|
42
32
|
requirements:
|
43
33
|
- - ">="
|
44
34
|
- !ruby/object:Gem::Version
|
45
|
-
hash: 3
|
46
|
-
segments:
|
47
|
-
- 0
|
48
35
|
version: "0"
|
49
36
|
type: :development
|
50
37
|
version_requirements: *id002
|
@@ -56,9 +43,6 @@ dependencies:
|
|
56
43
|
requirements:
|
57
44
|
- - ">="
|
58
45
|
- !ruby/object:Gem::Version
|
59
|
-
hash: 3
|
60
|
-
segments:
|
61
|
-
- 0
|
62
46
|
version: "0"
|
63
47
|
type: :development
|
64
48
|
version_requirements: *id003
|
@@ -70,23 +54,17 @@ dependencies:
|
|
70
54
|
requirements:
|
71
55
|
- - ">="
|
72
56
|
- !ruby/object:Gem::Version
|
73
|
-
hash: 3
|
74
|
-
segments:
|
75
|
-
- 0
|
76
57
|
version: "0"
|
77
58
|
type: :development
|
78
59
|
version_requirements: *id004
|
79
60
|
- !ruby/object:Gem::Dependency
|
80
|
-
name: ruby-
|
61
|
+
name: ruby-debug19
|
81
62
|
prerelease: false
|
82
63
|
requirement: &id005 !ruby/object:Gem::Requirement
|
83
64
|
none: false
|
84
65
|
requirements:
|
85
66
|
- - ">="
|
86
67
|
- !ruby/object:Gem::Version
|
87
|
-
hash: 3
|
88
|
-
segments:
|
89
|
-
- 0
|
90
68
|
version: "0"
|
91
69
|
type: :development
|
92
70
|
version_requirements: *id005
|
@@ -129,18 +107,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
129
107
|
requirements:
|
130
108
|
- - ">="
|
131
109
|
- !ruby/object:Gem::Version
|
132
|
-
hash: 3
|
133
|
-
segments:
|
134
|
-
- 0
|
135
110
|
version: "0"
|
136
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
112
|
none: false
|
138
113
|
requirements:
|
139
114
|
- - ">="
|
140
115
|
- !ruby/object:Gem::Version
|
141
|
-
hash: 3
|
142
|
-
segments:
|
143
|
-
- 0
|
144
116
|
version: "0"
|
145
117
|
requirements: []
|
146
118
|
|