lock_method 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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