redis-mutex 1.1.0 → 1.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/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