lock_method 0.1.3 → 0.2.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/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
|
|