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 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
- == Ignores arguments when locking a method
35
+ == Pays attention to method arguments
36
36
 
37
- lock_method ignores arguments when locking. So if you call Foo.bar(:a), calls to Foo.bar(:b) will be locked until the first call finishes.
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
- return unless ::File.exist? path(k)
10
- ::Marshal.load ::File.read(path(k))
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(v)
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
- dir = ::File.expand_path(::File.join(::Dir.tmpdir, 'lock_method'))
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
@@ -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
- if hsh = Config.instance.storage.get(cache_key)
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(options = {})
31
- options.each do |k, v|
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 expiry
58
- @expiry ||= ::Time.now + ttl
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
- # make sure these are set
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 existing_lock = Lock.find(cache_key)
83
- existing_lock.in_force?
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 obj_hash
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 call_original_method
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 original_method_id, *args
107
+ obj.send *original_method_id_and_args
122
108
  ensure
123
109
  delete
124
110
  end
@@ -1,3 +1,3 @@
1
1
  module LockMethod
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
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, :original_method_id => original_method_id, :args => args, :ttl => ttl
50
- lock.call_original_method
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 8
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 8
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 8
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 test_locked_method_return_value
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 test_locked_by_normally_terminating_process
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
- def test_module_method_locked_by_normally_terminating_process
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 test_locked_by_SIGKILLed_process
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 test_locked_by_killed_thread
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 test_locked_by_normally_finishing_thread
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 test_lock_instance_method
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 test_instance_method_lock_is_unique_to_instance
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 test_clear_instance_method_lock
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 test_clear_class_method_lock
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 test_expiring_lock
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
- segments:
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-12 23:00:00 -06:00
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-debug
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