lock_method 0.4.0 → 0.5.1

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/CHANGELOG ADDED
@@ -0,0 +1,16 @@
1
+ 0.5.1 / 2012-04-17
2
+
3
+ * Bug fixes
4
+
5
+ * Synchronize reading of locks among threads; previously only writing of locks had been synchronized.
6
+
7
+ 0.5.0 / 2012-04-17
8
+
9
+ * Enhancements
10
+
11
+ * Tested on MRI 1.8, MRI 1.9, and JRuby
12
+
13
+ * Bug fixes
14
+
15
+ * Proper (I hope use) used of mutexes
16
+ * More OS-friendly tmp file names
data/Gemfile CHANGED
@@ -1,4 +1,7 @@
1
- source "http://rubygems.org"
1
+ source :rubygems
2
2
 
3
3
  # Specify your gem's dependencies in cache_method.gemspec
4
4
  gemspec
5
+ gem 'test-unit'
6
+ gem 'dalli'
7
+ gem 'redis'
@@ -1,5 +1,5 @@
1
1
  require 'cache'
2
- require 'singleton'
2
+
3
3
  module LockMethod
4
4
  # Here's where you set config options.
5
5
  #
@@ -8,8 +8,10 @@ module LockMethod
8
8
  #
9
9
  # You'd probably put this in your Rails config/initializers, for example.
10
10
  class Config
11
- include ::Singleton
12
-
11
+ def initialize
12
+ @mutex = ::Mutex.new
13
+ end
14
+
13
15
  # Storage for keeping lockfiles.
14
16
  #
15
17
  # Defaults to using the filesystem's temp dir.
@@ -36,7 +38,9 @@ module LockMethod
36
38
  end
37
39
 
38
40
  def storage #:nodoc:
39
- @storage ||= DefaultStorageClient.instance
41
+ @storage || @mutex.synchronize do
42
+ @storage ||= DefaultStorageClient.new
43
+ end
40
44
  end
41
45
 
42
46
  # TTL for method caches. Defaults to 24 hours.
@@ -1,40 +1,49 @@
1
- require 'singleton'
2
1
  require 'tmpdir'
3
2
  require 'fileutils'
4
- require 'thread'
3
+ require 'digest/sha1'
4
+
5
5
  module LockMethod
6
6
  class DefaultStorageClient #:nodoc: all
7
-
8
- include ::Singleton
9
-
10
7
  class Entry
11
8
  attr_reader :created_at
12
9
  attr_reader :ttl
13
10
  attr_reader :v
14
11
  def initialize(ttl, v)
15
- @created_at = ::Time.now.to_f
16
- @ttl = ttl
12
+ @created_at = ::Time.now
13
+ @ttl = ttl.to_f
17
14
  @v = v
18
15
  end
19
16
  def expired?
20
- ttl.to_i > 0 and (::Time.now.to_f - created_at.to_f) > ttl.to_i
17
+ ttl > 0 and (::Time.now - created_at) > ttl
21
18
  end
22
19
  end
23
20
 
21
+ attr_reader :dir
22
+
23
+ def initialize
24
+ @mutex = ::Mutex.new
25
+ dir = ::File.expand_path ::File.join(::Dir.tmpdir, 'lock_method')
26
+ ::FileUtils.mkdir(dir) unless ::File.directory?(dir)
27
+ @dir = dir
28
+ end
29
+
24
30
  def get(k)
25
- if ::File.exist?(path(k)) and entry = ::Marshal.load(::File.read(path(k))) and not entry.expired?
26
- entry.v
31
+ path = path k
32
+ @mutex.synchronize do
33
+ if ::File.exist?(path) and (entry = ::Marshal.load(::File.read(path))) and not entry.expired?
34
+ entry.v
35
+ end
27
36
  end
28
- rescue ::Errno::ENOENT
37
+ rescue
38
+ $stderr.puts %{[lock_method] Rescued from #{$!.inspect} while trying to get a lock}
29
39
  end
30
40
 
31
41
  def set(k, v, ttl)
32
42
  entry = Entry.new ttl, v
33
- semaphore.synchronize do
34
- ::FileUtils.mkdir_p dir unless ::File.directory? dir
35
- ::File.open(path(k), ::File::RDWR|::File::CREAT, :external_encoding => 'ASCII-8BIT') do |f|
43
+ @mutex.synchronize do
44
+ ::File.open(path(k), 'wb') do |f|
36
45
  f.flock ::File::LOCK_EX
37
- f.write ::Marshal.dump(entry)
46
+ f.write ::Marshal.dump entry
38
47
  end
39
48
  end
40
49
  end
@@ -44,21 +53,15 @@ module LockMethod
44
53
  end
45
54
 
46
55
  def flush
47
- ::FileUtils.rm_rf dir
56
+ ::Dir["#{dir}/*.lock"].each do |path|
57
+ ::FileUtils.rm_f path
58
+ end
48
59
  end
49
-
60
+
50
61
  private
51
-
52
- def semaphore
53
- @semaphore ||= ::Mutex.new
54
- end
55
-
62
+
56
63
  def path(k)
57
- ::File.join dir, k
58
- end
59
-
60
- def dir
61
- ::File.expand_path ::File.join(::Dir.tmpdir, 'lock_method')
64
+ ::File.join dir, "#{::Digest::SHA1.hexdigest(k)}.lock"
62
65
  end
63
66
  end
64
67
  end
@@ -3,7 +3,7 @@ module LockMethod
3
3
  class Lock #:nodoc: all
4
4
  class << self
5
5
  def find(cache_key)
6
- Config.instance.storage.get cache_key
6
+ LockMethod.config.storage.get cache_key
7
7
  end
8
8
  def klass_name(obj)
9
9
  (obj.is_a?(::Class) or obj.is_a?(::Module)) ? obj.to_s : obj.class.to_s
@@ -14,6 +14,23 @@ module LockMethod
14
14
  def method_signature(obj, method_id)
15
15
  [ klass_name(obj), method_id ].join method_delimiter(obj)
16
16
  end
17
+ def resolve_lock(obj)
18
+ case obj
19
+ when ::Array
20
+ obj.map do |v|
21
+ resolve_lock v
22
+ end
23
+ when ::Hash
24
+ obj.inject({}) do |memo, (k, v)|
25
+ kk = resolve_lock k
26
+ vv = resolve_lock v
27
+ memo[kk] = vv
28
+ memo
29
+ end
30
+ else
31
+ obj.respond_to?(:as_lock) ? [obj.class.name, obj.as_lock] : obj
32
+ end
33
+ end
17
34
  end
18
35
 
19
36
  attr_reader :obj
@@ -21,6 +38,7 @@ module LockMethod
21
38
  attr_reader :args
22
39
 
23
40
  def initialize(obj, method_id, options = {})
41
+ @mutex = ::Mutex.new
24
42
  @obj = obj
25
43
  @method_id = method_id
26
44
  options = options.symbolize_keys
@@ -38,23 +56,27 @@ module LockMethod
38
56
  end
39
57
 
40
58
  def ttl
41
- @ttl ||= Config.instance.default_ttl
59
+ @ttl ||= LockMethod.config.default_ttl
42
60
  end
43
61
 
44
62
  def obj_digest
45
- @obj_digest ||= ::Digest::SHA1.hexdigest(::Marshal.dump(obj.respond_to?(:as_lock) ? obj.as_lock : obj))
63
+ @obj_digest || @mutex.synchronize do
64
+ @obj_digest ||= ::Digest::SHA1.hexdigest(::Marshal.dump(Lock.resolve_lock(obj)))
65
+ end
46
66
  end
47
67
 
48
68
  def args_digest
49
- @args_digest ||= args.to_a.empty? ? 'empty' : ::Digest::SHA1.hexdigest(::Marshal.dump(args))
69
+ @args_digest || @mutex.synchronize do
70
+ @args_digest ||= args.to_a.empty? ? 'empty' : ::Digest::SHA1.hexdigest(::Marshal.dump(Lock.resolve_lock(args)))
71
+ end
50
72
  end
51
73
 
52
74
  def delete
53
- Config.instance.storage.delete cache_key
75
+ LockMethod.config.storage.delete cache_key
54
76
  end
55
77
 
56
78
  def save
57
- Config.instance.storage.set cache_key, self, ttl
79
+ LockMethod.config.storage.set cache_key, self, ttl
58
80
  end
59
81
 
60
82
  def locked?
@@ -78,7 +100,7 @@ module LockMethod
78
100
  end
79
101
 
80
102
  def call_and_lock(*original_method_id_and_args)
81
- until !spin? or !locked?
103
+ while locked? and spin?
82
104
  ::Kernel.sleep 0.5
83
105
  end
84
106
  if locked?
@@ -86,7 +108,7 @@ module LockMethod
86
108
  else
87
109
  begin
88
110
  save
89
- obj.send *original_method_id_and_args
111
+ obj.send(*original_method_id_and_args)
90
112
  ensure
91
113
  delete
92
114
  end
@@ -1,3 +1,3 @@
1
1
  module LockMethod
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.1"
3
3
  end
data/lib/lock_method.rb CHANGED
@@ -4,20 +4,22 @@ if ::ActiveSupport::VERSION::MAJOR >= 3
4
4
  require 'active_support/core_ext'
5
5
  end
6
6
 
7
- require 'lock_method/version'
7
+ require 'lock_method/config'
8
+ require 'lock_method/lock'
9
+ require 'lock_method/default_storage_client'
8
10
 
9
11
  # See the README.rdoc for more info!
10
12
  module LockMethod
11
- autoload :Config, 'lock_method/config'
12
- autoload :Lock, 'lock_method/lock'
13
- autoload :DefaultStorageClient, 'lock_method/default_storage_client'
14
-
15
13
  # This is what gets raised when you try to run a locked method.
16
14
  class Locked < ::StandardError
17
15
  end
18
16
 
19
- def self.config #:nodoc:
20
- Config.instance
17
+ MUTEX = ::Mutex.new
18
+
19
+ def LockMethod.config #:nodoc:
20
+ @config || MUTEX.synchronize do
21
+ @config ||= Config.new
22
+ end
21
23
  end
22
24
 
23
25
  # All Objects, including instances and Classes, get the <tt>#lock_method_clear</tt> method.
@@ -65,14 +67,12 @@ module LockMethod
65
67
  define_method method_id do |*args1|
66
68
  options = options.merge(:args => args1)
67
69
  lock = ::LockMethod::Lock.new self, method_id, options
68
- lock.call_and_lock original_method_id, *args1
70
+ lock.call_and_lock(*([original_method_id]+args1))
69
71
  end
70
72
  end
71
73
  end
72
74
  end
73
75
 
74
- unless ::Object.method_defined? :lock_method
75
- ::Object.send :include, ::LockMethod::InstanceMethods
76
- ::Class.send :include, ::LockMethod::ClassMethods
77
- ::Module.extend ::LockMethod::ClassMethods
78
- end
76
+ ::Object.send :include, ::LockMethod::InstanceMethods
77
+ ::Class.send :include, ::LockMethod::ClassMethods
78
+ ::Module.send :include, ::LockMethod::ClassMethods
data/lock_method.gemspec CHANGED
@@ -1,6 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "lock_method/version"
2
+ require File.expand_path('../lib/lock_method/version', __FILE__)
4
3
 
5
4
  Gem::Specification.new do |s|
6
5
  s.name = "lock_method"
@@ -20,12 +19,4 @@ Gem::Specification.new do |s|
20
19
 
21
20
  s.add_runtime_dependency 'cache', '>=0.2.1'
22
21
  s.add_runtime_dependency 'activesupport'
23
- s.add_development_dependency 'test-unit'
24
- s.add_development_dependency 'memcached'
25
- s.add_development_dependency 'redis'
26
- if RUBY_VERSION >= '1.9'
27
- s.add_development_dependency 'ruby-debug19'
28
- else
29
- s.add_development_dependency 'ruby-debug'
30
- end
31
- end
22
+ end
data/test/helper.rb CHANGED
@@ -2,10 +2,7 @@ require 'rubygems'
2
2
  require 'bundler'
3
3
  Bundler.setup
4
4
  require 'test/unit'
5
- require 'ruby-debug'
6
5
  require 'shared_tests'
7
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
- $LOAD_PATH.unshift(File.dirname(__FILE__))
9
6
  require 'lock_method'
10
7
 
11
8
  class Blog1
@@ -1,10 +1,10 @@
1
1
  require 'helper'
2
2
 
3
- require 'memcached'
3
+ require 'dalli'
4
4
 
5
5
  class TestMemcachedStorage < Test::Unit::TestCase
6
6
  def setup
7
- my_cache = Memcached.new 'localhost:11211', :retry_timeout => 1, :server_failure_limit => 25
7
+ my_cache = Dalli::Client.new 'localhost:11211'
8
8
  my_cache.flush
9
9
  LockMethod.config.storage = my_cache
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lock_method
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-01 00:00:00.000000000 Z
12
+ date: 2012-04-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cache
16
- requirement: &2152246540 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,62 +21,28 @@ dependencies:
21
21
  version: 0.2.1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2152246540
25
- - !ruby/object:Gem::Dependency
26
- name: activesupport
27
- requirement: &2152240680 !ruby/object:Gem::Requirement
28
- none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: *2152240680
36
- - !ruby/object:Gem::Dependency
37
- name: test-unit
38
- requirement: &2160094560 !ruby/object:Gem::Requirement
39
- none: false
40
- requirements:
41
- - - ! '>='
42
- - !ruby/object:Gem::Version
43
- version: '0'
44
- type: :development
45
- prerelease: false
46
- version_requirements: *2160094560
47
- - !ruby/object:Gem::Dependency
48
- name: memcached
49
- requirement: &2160090340 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
50
25
  none: false
51
26
  requirements:
52
27
  - - ! '>='
53
28
  - !ruby/object:Gem::Version
54
- version: '0'
55
- type: :development
56
- prerelease: false
57
- version_requirements: *2160090340
29
+ version: 0.2.1
58
30
  - !ruby/object:Gem::Dependency
59
- name: redis
60
- requirement: &2160103240 !ruby/object:Gem::Requirement
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
61
33
  none: false
62
34
  requirements:
63
35
  - - ! '>='
64
36
  - !ruby/object:Gem::Version
65
37
  version: '0'
66
- type: :development
38
+ type: :runtime
67
39
  prerelease: false
68
- version_requirements: *2160103240
69
- - !ruby/object:Gem::Dependency
70
- name: ruby-debug19
71
- requirement: &2160101140 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
72
41
  none: false
73
42
  requirements:
74
43
  - - ! '>='
75
44
  - !ruby/object:Gem::Version
76
45
  version: '0'
77
- type: :development
78
- prerelease: false
79
- version_requirements: *2160101140
80
46
  description: Like alias_method, but it's lock_method!
81
47
  email:
82
48
  - seamus@abshere.net
@@ -85,6 +51,7 @@ extensions: []
85
51
  extra_rdoc_files: []
86
52
  files:
87
53
  - .gitignore
54
+ - CHANGELOG
88
55
  - Gemfile
89
56
  - README.rdoc
90
57
  - Rakefile
@@ -119,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
86
  version: '0'
120
87
  requirements: []
121
88
  rubyforge_project: lock_method
122
- rubygems_version: 1.8.15
89
+ rubygems_version: 1.8.21
123
90
  signing_key:
124
91
  specification_version: 3
125
92
  summary: Lets you lock methods (to memcached, redis, etc.) as though you had a lockfile