redis-mutex 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,7 +3,7 @@ source "http://rubygems.org"
3
3
  gem "redis-classy", "~> 1.0.0"
4
4
 
5
5
  group :development do
6
- gem "rspec", "~> 2.6.0"
7
- gem "bundler", "~> 1.0.0"
8
- gem "jeweler", "~> 1.6.0"
6
+ gem "rspec"
7
+ gem "bundler"
8
+ gem "jeweler"
9
9
  end
data/Gemfile.lock CHANGED
@@ -1,32 +1,32 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- diff-lcs (1.1.2)
4
+ diff-lcs (1.1.3)
5
5
  git (1.2.5)
6
- jeweler (1.6.0)
7
- bundler (~> 1.0.0)
6
+ jeweler (1.6.4)
7
+ bundler (~> 1.0)
8
8
  git (>= 1.2.5)
9
9
  rake
10
- rake (0.9.0)
11
- redis (2.2.0)
10
+ rake (0.9.2.2)
11
+ redis (2.2.2)
12
12
  redis-classy (1.0.0)
13
13
  redis-namespace (~> 1.0.0)
14
14
  redis-namespace (1.0.3)
15
15
  redis (< 3.0.0)
16
- rspec (2.6.0)
17
- rspec-core (~> 2.6.0)
18
- rspec-expectations (~> 2.6.0)
19
- rspec-mocks (~> 2.6.0)
20
- rspec-core (2.6.2)
21
- rspec-expectations (2.6.0)
16
+ rspec (2.7.0)
17
+ rspec-core (~> 2.7.0)
18
+ rspec-expectations (~> 2.7.0)
19
+ rspec-mocks (~> 2.7.0)
20
+ rspec-core (2.7.1)
21
+ rspec-expectations (2.7.0)
22
22
  diff-lcs (~> 1.1.2)
23
- rspec-mocks (2.6.0)
23
+ rspec-mocks (2.7.0)
24
24
 
25
25
  PLATFORMS
26
26
  ruby
27
27
 
28
28
  DEPENDENCIES
29
- bundler (~> 1.0.0)
30
- jeweler (~> 1.6.0)
29
+ bundler
30
+ jeweler
31
31
  redis-classy (~> 1.0.0)
32
- rspec (~> 2.6.0)
32
+ rspec
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ Redis Mutex
2
+ ===========
3
+
4
+ Distrubuted mutex in Ruby using Redis. Supports both **blocking** and **non-blocking** semantics.
5
+
6
+ The idea was taken from [the official SETNX doc](http://redis.io/commands/setnx).
7
+
8
+ Synopsis
9
+ --------
10
+
11
+ In the following example, only one thread / process / server can enter the locked block at one time.
12
+
13
+ ```ruby
14
+ mutex = Redis::Mutex.new(:your_lock_name)
15
+ mutex.lock do
16
+ # do something exclusively
17
+ end
18
+ ```
19
+
20
+ By default, when one is holding a lock, others wait **1 second** in total, polling **every 100ms** to see if the lock was released.
21
+ When 1 second has passed, the lock method returns `false`.
22
+
23
+ If you want to immediately receive `false` on an unsuccessful locking attempt, you can configure the mutex to work in the non-blocking mode, as explained later.
24
+
25
+ Install
26
+ -------
27
+
28
+ gem install redis-mutex
29
+
30
+ Usage
31
+ -----
32
+
33
+ In Gemfile:
34
+
35
+ ```ruby
36
+ gem "redis-mutex"
37
+ ```
38
+
39
+ Register the Redis server: (e.g. in `config/initializers/redis_mutex.rb` for Rails)
40
+
41
+ ```ruby
42
+ Redis::Classy.db = Redis.new(:host => 'localhost')
43
+ ```
44
+
45
+ Note that Redis Mutex uses the `redis-classy` gem internally.
46
+
47
+ There are four methods - `new`, `lock`, `unlock` and `sweep`:
48
+
49
+ ```ruby
50
+ mutex = Redis::Mutex.new(key, options)
51
+ mutex.lock
52
+ mutex.unlock
53
+ Redis::Mutex.sweep
54
+ ```
55
+
56
+ For the key, it takes any Ruby objects that respond to :id, where the key is automatically set as "TheClass:id",
57
+ or pass any string or symbol.
58
+
59
+ Also the initialize method takes several options.
60
+
61
+ ```ruby
62
+ :block => 1 # Specify in seconds how long you want to wait for the lock to be released.
63
+ # Speficy 0 if you need non-blocking sematics and return false immediately. (default: 1)
64
+ :sleep => 0.1 # Specify in seconds how long the polling interval should be when :block is given.
65
+ # It is recommended that you do NOT go below 0.01. (default: 0.1)
66
+ :expire => 10 # Specify in seconds when the lock should forcibly be removed when something went wrong
67
+ # with the one who held the lock. (default: 10)
68
+ ```
69
+
70
+ The lock method returns `true` when the lock has been successfully obtained, or returns `false` when the attempts
71
+ failed after the seconds specified with **:block**. It immediately returns `false` when 0 is given to **:block**.
72
+
73
+ Here's a sample usage in a Rails app:
74
+
75
+ ```ruby
76
+ class RoomController < ApplicationController
77
+ def enter
78
+ @room = Room.find(params[:id])
79
+
80
+ mutex = Redis::Mutex.new(@room) # key => "Room:123"
81
+ mutex.lock do
82
+ # do something exclusively
83
+ end
84
+ end
85
+ end
86
+ ```
87
+
88
+ Note that you need to explicitly call the unlock method unless you don't use the block syntax.
89
+
90
+ In the following example,
91
+
92
+ ```ruby
93
+ def enter
94
+ mutex = Redis::Mutex.new('non-blocking', :block => 0, :expire => 10.minutes)
95
+ mutex.lock
96
+ # do something exclusively
97
+ mutex.unlock
98
+ rescue
99
+ mutex.unlock
100
+ raise
101
+ end
102
+ ```
103
+
104
+ Also note that, internally, the actual key is structured in the following form:
105
+
106
+ ```ruby
107
+ Redis.new.keys
108
+ => ["Redis::Mutex:Room:111", "Redis::Mutex:Room:112", ... ]
109
+ ```
110
+
111
+ The automatic prefixing and binding is the feature of `Redis::Classy`.
112
+ For more internal details, refer to [Redis Classy](https://github.com/kenn/redis-classy).
data/Rakefile CHANGED
@@ -15,8 +15,8 @@ Jeweler::Tasks.new do |gem|
15
15
  gem.name = "redis-mutex"
16
16
  gem.homepage = "http://github.com/kenn/redis-mutex"
17
17
  gem.license = "MIT"
18
- gem.summary = "Distrubuted non-blocking mutex in Ruby using Redis"
19
- gem.description = "Distrubuted non-blocking mutex in Ruby using Redis"
18
+ gem.summary = "Distrubuted mutex using Redis"
19
+ gem.description = "Distrubuted mutex using Redis"
20
20
  gem.email = "kenn.ejima@gmail.com"
21
21
  gem.authors = ["Kenn Ejima"]
22
22
  end
@@ -31,5 +31,5 @@ end
31
31
 
32
32
  task :default => :spec
33
33
  task :spec do
34
- exec "rspec spec/redis_mutex_spec.rb"
34
+ exec "rspec spec"
35
35
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.2.0
data/lib/redis/mutex.rb CHANGED
@@ -1,46 +1,51 @@
1
1
  class Redis
2
2
  #
3
- # Redis::Mutex options
3
+ # Options
4
4
  #
5
5
  # :block => Specify in seconds how long you want to wait for the lock to be released. Speficy 0
6
6
  # if you need non-blocking sematics and return false immediately. (default: 1)
7
7
  # :sleep => Specify in seconds how long the polling interval should be when :block is given.
8
8
  # It is recommended that you do NOT go below 0.01. (default: 0.1)
9
9
  # :expire => Specify in seconds when the lock should forcibly be removed when something went wrong
10
- # with the one who held the lock. (in seconds, default: 10)
10
+ # with the one who held the lock. (default: 10)
11
11
  #
12
12
  class Mutex < Redis::Classy
13
-
13
+ autoload :Macro, 'redis/mutex/macro'
14
+ attr_reader :block, :sleep, :expire, :locking
14
15
  DEFAULT_EXPIRE = 10
15
- attr_accessor :options
16
16
 
17
17
  def initialize(object, options={})
18
18
  super(object.is_a?(String) || object.is_a?(Symbol) ? object : "#{object.class.name}:#{object.id}")
19
- @options = options
20
- @options[:block] ||= 1
21
- @options[:sleep] ||= 0.1
22
- @options[:expire] ||= DEFAULT_EXPIRE
19
+ @block = options[:block] || 1
20
+ @sleep = options[:sleep] || 0.1
21
+ @expire = options[:expire] || DEFAULT_EXPIRE
23
22
  end
24
23
 
25
24
  def lock
26
- if @options[:block] > 0
25
+ @locking = false
26
+
27
+ if @block > 0
28
+ # Blocking mode
27
29
  start_at = Time.now
28
- success = false
29
- while Time.now - start_at < @options[:block]
30
- success = true and break if try_lock
31
- sleep @options[:sleep]
30
+ while Time.now - start_at < @block
31
+ @locking = true and break if try_lock
32
+ Kernel.sleep @sleep
32
33
  end
33
34
  else
34
- # Non-blocking
35
- success = try_lock
35
+ # Non-blocking mode
36
+ @locking = try_lock
36
37
  end
38
+ success = @locking # Backup
37
39
 
38
- if block_given? and success
39
- yield
40
- # Since it's possible that the yielded operation took a long time, we can't just simply
41
- # Release the lock. The unlock method checks if the expires_at remains the same that you
42
- # set, and do not release it when the lock timestamp was overwritten.
43
- unlock
40
+ if block_given? and @locking
41
+ begin
42
+ yield
43
+ ensure
44
+ # Since it's possible that the yielded operation took a long time, we can't just simply
45
+ # Release the lock. The unlock method checks if the expires_at remains the same that you
46
+ # set, and do not release it when the lock timestamp was overwritten.
47
+ unlock
48
+ end
44
49
  end
45
50
 
46
51
  success
@@ -48,7 +53,7 @@ class Redis
48
53
 
49
54
  def try_lock
50
55
  now = Time.now.to_f
51
- @expires_at = now + @options[:expire] # Extend in each blocking loop
56
+ @expires_at = now + @expire # Extend in each blocking loop
52
57
  return true if setnx(@expires_at) # Success, the lock has been acquired
53
58
  return false if get.to_f > now # Check if the lock is still effective
54
59
 
@@ -58,26 +63,33 @@ class Redis
58
63
  end
59
64
 
60
65
  def unlock(force=false)
66
+ @locking = false
61
67
  del if get.to_f == @expires_at or force # Release the lock if it seems to be yours
62
68
  end
63
69
 
64
- def self.sweep
65
- return 0 if (all_keys = keys).empty?
70
+ class << self
71
+ def sweep
72
+ return 0 if (all_keys = keys).empty?
66
73
 
67
- now = Time.now.to_f
68
- values = mget(*all_keys)
74
+ now = Time.now.to_f
75
+ values = mget(*all_keys)
69
76
 
70
- expired_keys = [].tap do |array|
71
- all_keys.each_with_index do |key, i|
72
- array << key if !values[i].nil? and values[i].to_f <= now
77
+ expired_keys = [].tap do |array|
78
+ all_keys.each_with_index do |key, i|
79
+ array << key if !values[i].nil? and values[i].to_f <= now
80
+ end
81
+ end
82
+
83
+ expired_keys.each do |key|
84
+ del(key) if getset(key, now + DEFAULT_EXPIRE).to_f <= now # Make extra sure that anyone haven't extended the lock
73
85
  end
74
- end
75
86
 
76
- expired_keys.each do |key|
77
- del(key) if getset(key, now + DEFAULT_EXPIRE).to_f <= now # Make extra sure that anyone haven't extended the lock
87
+ expired_keys.size
78
88
  end
79
89
 
80
- expired_keys.size
90
+ def lock(object, options={}, &block)
91
+ new(object, options).lock(&block)
92
+ end
81
93
  end
82
94
  end
83
95
  end
@@ -0,0 +1,41 @@
1
+ class Redis
2
+ class Mutex
3
+ module Macro
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.class_eval do
7
+ class << self
8
+ attr_accessor :auto_mutex_methods
9
+ end
10
+ @auto_mutex_methods = {}
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def auto_mutex(target, options={})
16
+ self.auto_mutex_methods[target] = options
17
+ end
18
+
19
+ def method_added(target)
20
+ return if target.to_s =~ /^auto_mutex_methods.*$/
21
+ return unless self.auto_mutex_methods[target]
22
+ without_method = "#{target}_without_auto_mutex"
23
+ with_method = "#{target}_with_auto_mutex"
24
+ return if method_defined?(without_method)
25
+
26
+ define_method(with_method) do |*args|
27
+ key = self.class.name << '#' << target.to_s
28
+ options = self.class.auto_mutex_methods[target]
29
+
30
+ Redis::Mutex.lock(key, options) do
31
+ send(without_method, *args)
32
+ end
33
+ end
34
+
35
+ alias_method without_method, target
36
+ alias_method target, with_method
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
data/redis-mutex.gemspec CHANGED
@@ -4,17 +4,17 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{redis-mutex}
8
- s.version = "1.1.0"
7
+ s.name = "redis-mutex"
8
+ s.version = "1.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kenn Ejima"]
12
- s.date = %q{2011-05-21}
13
- s.description = %q{Distrubuted non-blocking mutex in Ruby using Redis}
14
- s.email = %q{kenn.ejima@gmail.com}
12
+ s.date = "2011-11-05"
13
+ s.description = "Distrubuted mutex using Redis"
14
+ s.email = "kenn.ejima@gmail.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.txt",
17
- "README.rdoc"
17
+ "README.md"
18
18
  ]
19
19
  s.files = [
20
20
  ".document",
@@ -22,40 +22,41 @@ Gem::Specification.new do |s|
22
22
  "Gemfile",
23
23
  "Gemfile.lock",
24
24
  "LICENSE.txt",
25
- "README.rdoc",
25
+ "README.md",
26
26
  "Rakefile",
27
27
  "VERSION",
28
28
  "lib/redis-mutex.rb",
29
29
  "lib/redis/mutex.rb",
30
+ "lib/redis/mutex/macro.rb",
30
31
  "redis-mutex.gemspec",
31
32
  "spec/redis_mutex_spec.rb",
32
33
  "spec/spec_helper.rb"
33
34
  ]
34
- s.homepage = %q{http://github.com/kenn/redis-mutex}
35
+ s.homepage = "http://github.com/kenn/redis-mutex"
35
36
  s.licenses = ["MIT"]
36
37
  s.require_paths = ["lib"]
37
- s.rubygems_version = %q{1.6.2}
38
- s.summary = %q{Distrubuted non-blocking mutex in Ruby using Redis}
38
+ s.rubygems_version = "1.8.11"
39
+ s.summary = "Distrubuted mutex using Redis"
39
40
 
40
41
  if s.respond_to? :specification_version then
41
42
  s.specification_version = 3
42
43
 
43
44
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
45
  s.add_runtime_dependency(%q<redis-classy>, ["~> 1.0.0"])
45
- s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
46
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
47
- s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
46
+ s.add_development_dependency(%q<rspec>, [">= 0"])
47
+ s.add_development_dependency(%q<bundler>, [">= 0"])
48
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
48
49
  else
49
50
  s.add_dependency(%q<redis-classy>, ["~> 1.0.0"])
50
- s.add_dependency(%q<rspec>, ["~> 2.6.0"])
51
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
52
- s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
51
+ s.add_dependency(%q<rspec>, [">= 0"])
52
+ s.add_dependency(%q<bundler>, [">= 0"])
53
+ s.add_dependency(%q<jeweler>, [">= 0"])
53
54
  end
54
55
  else
55
56
  s.add_dependency(%q<redis-classy>, ["~> 1.0.0"])
56
- s.add_dependency(%q<rspec>, ["~> 2.6.0"])
57
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
58
- s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
57
+ s.add_dependency(%q<rspec>, [">= 0"])
58
+ s.add_dependency(%q<bundler>, [">= 0"])
59
+ s.add_dependency(%q<jeweler>, [">= 0"])
59
60
  end
60
61
  end
61
62
 
@@ -3,6 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
  describe Redis::Mutex do
4
4
  before do
5
5
  Redis::Classy.flushdb
6
+ @short_mutex_options = { :block => 0.1, :sleep => 0.02 }
6
7
  end
7
8
 
8
9
  after do
@@ -32,10 +33,10 @@ describe Redis::Mutex do
32
33
  end
33
34
 
34
35
  it "should not get a lock when existing lock is still effective" do
35
- mutex = Redis::Mutex.new(:test_lock, :block => 0.2)
36
+ mutex = Redis::Mutex.new(:test_lock, @short_mutex_options)
36
37
 
37
38
  # someone beats us to it
38
- mutex2 = Redis::Mutex.new(:test_lock, :block => 0.2)
39
+ mutex2 = Redis::Mutex.new(:test_lock, @short_mutex_options)
39
40
  mutex2.lock
40
41
 
41
42
  mutex.lock.should be_false # should not have the lock
@@ -54,4 +55,47 @@ describe Redis::Mutex do
54
55
  mutex.unlock
55
56
  mutex.get.should_not be_nil # lock should still be there
56
57
  end
58
+
59
+ it "should ensure unlock when something goes wrong in the block" do
60
+ mutex = Redis::Mutex.new(:test_lock)
61
+ begin
62
+ mutex.lock do
63
+ raise "Something went wrong!"
64
+ end
65
+ rescue
66
+ mutex.locking.should be_false
67
+ end
68
+ end
69
+
70
+ it "should reset locking state on reuse" do
71
+ mutex = Redis::Mutex.new(:test_lock, @short_mutex_options)
72
+ mutex.lock.should be_true
73
+ mutex.lock.should be_false
74
+ end
75
+
76
+ describe Redis::Mutex::Macro do
77
+ it "should add auto_mutex" do
78
+
79
+ class C
80
+ include Redis::Mutex::Macro
81
+ auto_mutex :run_singularly, :block => 0 # Give up immediately if lock is taken
82
+ @@result = 0
83
+
84
+ def run_singularly
85
+ sleep 0.1
86
+ Thread.exclusive { @@result += 1 }
87
+ end
88
+
89
+ def self.result
90
+ @@result
91
+ end
92
+ end
93
+
94
+ t1 = Thread.new { C.new.run_singularly }
95
+ t2 = Thread.new { C.new.run_singularly }
96
+ t1.join
97
+ t2.join
98
+ C.result.should == 1
99
+ end
100
+ end
57
101
  end
data/spec/spec_helper.rb CHANGED
@@ -11,7 +11,7 @@ RSpec.configure do |config|
11
11
  # Use database 15 for testing so we don't accidentally step on you real data.
12
12
  Redis::Classy.db = Redis.new(:db => 15)
13
13
  unless Redis::Classy.keys.empty?
14
- puts '[ERROR]: Redis database 15 not empty! run "rake flushdb" beforehand.'
14
+ puts '[ERROR]: Redis database 15 not empty! If you are sure, run "rake flushdb" beforehand.'
15
15
  exit!
16
16
  end
17
17
  end
metadata CHANGED
@@ -1,115 +1,108 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: redis-mutex
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
4
5
  prerelease:
5
- version: 1.1.0
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Kenn Ejima
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-05-21 00:00:00 -07:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
12
+ date: 2011-11-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
17
15
  name: redis-classy
18
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &2153901140 !ruby/object:Gem::Requirement
19
17
  none: false
20
- requirements:
18
+ requirements:
21
19
  - - ~>
22
- - !ruby/object:Gem::Version
20
+ - !ruby/object:Gem::Version
23
21
  version: 1.0.0
24
22
  type: :runtime
25
23
  prerelease: false
26
- version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
24
+ version_requirements: *2153901140
25
+ - !ruby/object:Gem::Dependency
28
26
  name: rspec
29
- requirement: &id002 !ruby/object:Gem::Requirement
27
+ requirement: &2153900520 !ruby/object:Gem::Requirement
30
28
  none: false
31
- requirements:
32
- - - ~>
33
- - !ruby/object:Gem::Version
34
- version: 2.6.0
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
35
33
  type: :development
36
34
  prerelease: false
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
35
+ version_requirements: *2153900520
36
+ - !ruby/object:Gem::Dependency
39
37
  name: bundler
40
- requirement: &id003 !ruby/object:Gem::Requirement
38
+ requirement: &2153899800 !ruby/object:Gem::Requirement
41
39
  none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: 1.0.0
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
46
44
  type: :development
47
45
  prerelease: false
48
- version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
46
+ version_requirements: *2153899800
47
+ - !ruby/object:Gem::Dependency
50
48
  name: jeweler
51
- requirement: &id004 !ruby/object:Gem::Requirement
49
+ requirement: &2153899020 !ruby/object:Gem::Requirement
52
50
  none: false
53
- requirements:
54
- - - ~>
55
- - !ruby/object:Gem::Version
56
- version: 1.6.0
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
57
55
  type: :development
58
56
  prerelease: false
59
- version_requirements: *id004
60
- description: Distrubuted non-blocking mutex in Ruby using Redis
57
+ version_requirements: *2153899020
58
+ description: Distrubuted mutex using Redis
61
59
  email: kenn.ejima@gmail.com
62
60
  executables: []
63
-
64
61
  extensions: []
65
-
66
- extra_rdoc_files:
62
+ extra_rdoc_files:
67
63
  - LICENSE.txt
68
- - README.rdoc
69
- files:
64
+ - README.md
65
+ files:
70
66
  - .document
71
67
  - .rspec
72
68
  - Gemfile
73
69
  - Gemfile.lock
74
70
  - LICENSE.txt
75
- - README.rdoc
71
+ - README.md
76
72
  - Rakefile
77
73
  - VERSION
78
74
  - lib/redis-mutex.rb
79
75
  - lib/redis/mutex.rb
76
+ - lib/redis/mutex/macro.rb
80
77
  - redis-mutex.gemspec
81
78
  - spec/redis_mutex_spec.rb
82
79
  - spec/spec_helper.rb
83
- has_rdoc: true
84
80
  homepage: http://github.com/kenn/redis-mutex
85
- licenses:
81
+ licenses:
86
82
  - MIT
87
83
  post_install_message:
88
84
  rdoc_options: []
89
-
90
- require_paths:
85
+ require_paths:
91
86
  - lib
92
- required_ruby_version: !ruby/object:Gem::Requirement
87
+ required_ruby_version: !ruby/object:Gem::Requirement
93
88
  none: false
94
- requirements:
95
- - - ">="
96
- - !ruby/object:Gem::Version
97
- hash: 3421574840884084281
98
- segments:
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ segments:
99
94
  - 0
100
- version: "0"
101
- required_rubygems_version: !ruby/object:Gem::Requirement
95
+ hash: 3163934937932082133
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
97
  none: false
103
- requirements:
104
- - - ">="
105
- - !ruby/object:Gem::Version
106
- version: "0"
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
107
102
  requirements: []
108
-
109
103
  rubyforge_project:
110
- rubygems_version: 1.6.2
104
+ rubygems_version: 1.8.11
111
105
  signing_key:
112
106
  specification_version: 3
113
- summary: Distrubuted non-blocking mutex in Ruby using Redis
107
+ summary: Distrubuted mutex using Redis
114
108
  test_files: []
115
-
data/README.rdoc DELETED
@@ -1,80 +0,0 @@
1
- = Redis Mutex
2
-
3
- Distrubuted mutex in Ruby using Redis. Supports both blocking and non-blocking semantics.
4
-
5
- The idea was taken from http://redis.io/commands/setnx and http://gist.github.com/457269,
6
- also block syntax was inspired by https://github.com/nateware/redis-objects.
7
-
8
- Requires the redis-classy gem.
9
-
10
- == Synopsis
11
-
12
- Initialize a Redis::Mutex object with a key, then only one thread / process / server can
13
- enter the locked block at a time.
14
-
15
- mutex = Redis::Mutex.new(:lock_name)
16
- mutex.lock do
17
- do_something
18
- end
19
-
20
- By default, when one is running the locked block, others wait 1 second, polling interval is 100ms.
21
- If 1 second has passed, the lock method returns false.
22
-
23
- == Install
24
-
25
- gem install redis-mutex
26
-
27
- == Usage
28
-
29
- In Gemfile:
30
-
31
- gem "redis-mutex"
32
-
33
- Create a reference to Redis. (e.g. in config/initializers/redis_mutex.rb for Rails)
34
-
35
- Redis::Classy.db = Redis.new(:host => 'localhost')
36
-
37
- There are four methods - new, lock, unlock and sweep:
38
-
39
- mutex = Redis::Mutex.new(key, options)
40
- mutex.lock
41
- mutex.unlock
42
- Redis::Mutex.sweep
43
-
44
- For the key, it takes any Ruby objects that respond to :id, where the key is automatically set as "TheClass:id",
45
- or pass any string or symbol.
46
-
47
- Also the initialize method takes several options.
48
-
49
- :block => Specify in seconds how long you want to wait for the lock to be released. Speficy 0
50
- if you need non-blocking sematics and return false immediately. (default: 1)
51
- :sleep => Specify in seconds how long the polling interval should be when :block is given.
52
- It is recommended that you do NOT go below 0.01. (default: 0.1)
53
- :expire => Specify in seconds when the lock should forcibly be removed when something went wrong
54
- with the one who held the lock. (in seconds, default: 10)
55
-
56
- The lock method returns true when the lock has been successfully obtained, or returns false when the attempts
57
- failed after the seconds specified with :block. It immediately returns false when 0 is given to :block.
58
-
59
- Here's a sample usage in a Rails app:
60
-
61
- class RoomController < ApplicationController
62
- def enter
63
- @room = Room.find_by_id(params[:id])
64
-
65
- mutex = Redis::Mutex.new(@room) # key => "Room:123"
66
- mutex.lock do
67
- do_something
68
- end
69
- end
70
- end
71
-
72
- Note that you need to explicitly call the unlock method unless you don't use the block syntax.
73
-
74
- Also note that, if you take a closer look, you find that the actual key is structured in the following form:
75
-
76
- Redis.new.keys
77
- => ["Redis::Mutex:Room:123"]
78
-
79
- The automatic prefixing and binding is the feature of Redis::Classy.
80
- For more internal details, refer to https://github.com/kenn/redis-classy