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 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