remote_lock 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +94 -0
- data/Rakefile +26 -0
- data/lib/remote_lock.rb +57 -0
- data/lib/remote_lock/adapters/base.rb +40 -0
- data/lib/remote_lock/adapters/memcached.rb +20 -0
- data/lib/remote_lock/adapters/redis.rb +21 -0
- data/lib/remote_lock/version.rb +3 -0
- data/spec/adapters/hash_spec.rb +5 -0
- data/spec/adapters/memcached_spec.rb +47 -0
- data/spec/adapters/redis_spec.rb +61 -0
- data/spec/memcache.yml +2 -0
- data/spec/remote_lock_spec.rb +150 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/behave_as_a_remote_lock_adapter.rb +27 -0
- data/spec/support/memcached_storage.rb +7 -0
- data/spec/support/redis_storage.rb +3 -0
- metadata +199 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 HouseTrip
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/HouseTrip/remote_lock.png)](https://travis-ci.org/HouseTrip/remote_lock)
|
2
|
+
|
3
|
+
remote_lock
|
4
|
+
===========
|
5
|
+
|
6
|
+
This is a rewrite of a initial extraction from Nick Kallen's [cache-money](http://github.com/nkallen/cache-money) and
|
7
|
+
also a fork from James Golick [memcache-lock](https://github.com/jamesgolick/memcache-lock)
|
8
|
+
|
9
|
+
This adds supports for memcache or redis as lock storage.
|
10
|
+
|
11
|
+
Installation
|
12
|
+
------------
|
13
|
+
|
14
|
+
```shell
|
15
|
+
gem install remote-lock
|
16
|
+
```
|
17
|
+
|
18
|
+
Initialization
|
19
|
+
-------------
|
20
|
+
|
21
|
+
* Lock using memcached:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# memcache = MemCache.new(YAML.load(File.read("/path/to/memcache/config")))
|
25
|
+
# Or whatever way you have your memcache connection
|
26
|
+
$lock = RemoteLock.new(RemoteLock::Adapters::Memcached.new(memcache))
|
27
|
+
```
|
28
|
+
|
29
|
+
* Lock using redis:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# redis = Redis.new
|
33
|
+
# Or whatever way you have your redis connection
|
34
|
+
$lock = RemoteLock.new(RemoteLock::Adapters::Redis.new(redis))
|
35
|
+
```
|
36
|
+
|
37
|
+
Usage
|
38
|
+
-----
|
39
|
+
|
40
|
+
Then, wherever you'd like to lock a key, use it like this:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
$lock.synchronize("some-key") do
|
44
|
+
# stuff that needs synchronization in here
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Options:
|
49
|
+
|
50
|
+
* TTL
|
51
|
+
|
52
|
+
By default keys will expire after 60 seconds, you can define this per key:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
$lock.synchronize("my-key", expiry: 30.seconds) do ... end
|
56
|
+
```
|
57
|
+
|
58
|
+
* Attempts
|
59
|
+
|
60
|
+
By default it will try 11 times to lock the resource, this can be set per key:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
$lock.synchronize("my-key", retries: 5) do ... end
|
64
|
+
```
|
65
|
+
|
66
|
+
* Tries interval
|
67
|
+
|
68
|
+
You can customize the interval between tries, initially it's 10ms:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
$lock.synchronize("my-key", initial_wait: 10e-3) do ... end
|
72
|
+
```
|
73
|
+
|
74
|
+
For more info, see lib/remote_lock.rb. It's very straightforward to read.
|
75
|
+
|
76
|
+
Note on Patches/Pull Requests
|
77
|
+
=============================
|
78
|
+
|
79
|
+
* Fork the project.
|
80
|
+
* Make your feature addition or bug fix.
|
81
|
+
* Add tests for it. This is important so I don't break it in a
|
82
|
+
future version unintentionally.
|
83
|
+
* Commit, do not mess with rakefile, version, or history.
|
84
|
+
(if you want to have your own version, that is fine but
|
85
|
+
bump version in a commit by itself I can ignore when I pull)
|
86
|
+
* Send me a pull request. Bonus points for topic branches.
|
87
|
+
|
88
|
+
License
|
89
|
+
============
|
90
|
+
MIT licence. Copyright (c) 2013 HouseTrip Ltd.
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
Based on the memcache-lock gem: https://github.com/jamesgolick/memcache-lock
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
|
6
|
+
desc 'Run spec tests'
|
7
|
+
task :spec do
|
8
|
+
RSpec::Core::RakeTask.new do |spec|
|
9
|
+
spec.pattern = "./spec/**/*_spec.rb"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :spec
|
14
|
+
|
15
|
+
Rake::RDocTask.new do |rdoc|
|
16
|
+
if File.exist?('VERSION')
|
17
|
+
version = File.read('VERSION')
|
18
|
+
else
|
19
|
+
version = ""
|
20
|
+
end
|
21
|
+
|
22
|
+
rdoc.rdoc_dir = 'rdoc'
|
23
|
+
rdoc.title = "memcache-lock #{version}"
|
24
|
+
rdoc.rdoc_files.include('README*')
|
25
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
26
|
+
end
|
data/lib/remote_lock.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
class RemoteLock
|
2
|
+
class Error < RuntimeError; end
|
3
|
+
|
4
|
+
DEFAULT_OPTIONS = {
|
5
|
+
:initial_wait => 10e-3, # seconds -- first soft fail will wait for 10ms
|
6
|
+
:expiry => 60, # seconds
|
7
|
+
:retries => 11, # these defaults will retry for a total 41sec max
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(adapter, prefix = nil)
|
11
|
+
raise "Invalid Adapter" unless Adapters::Base.valid?(adapter)
|
12
|
+
@adapter = adapter
|
13
|
+
@prefix = prefix
|
14
|
+
end
|
15
|
+
|
16
|
+
def synchronize(key, options={})
|
17
|
+
if acquired?(key)
|
18
|
+
yield
|
19
|
+
else
|
20
|
+
acquire_lock(key, options)
|
21
|
+
begin
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
release_lock(key)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def acquire_lock(key, options = {})
|
30
|
+
options = DEFAULT_OPTIONS.merge(options)
|
31
|
+
1.upto(options[:retries]) do |attempt|
|
32
|
+
success = @adapter.store(key_for(key), options[:expiry])
|
33
|
+
return if success
|
34
|
+
break if attempt == options[:retries]
|
35
|
+
Kernel.sleep(2 ** (attempt + rand - 1) * options[:initial_wait])
|
36
|
+
end
|
37
|
+
raise RemoteLock::Error, "Couldn't acquire lock for: #{key}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def release_lock(key)
|
41
|
+
@adapter.delete(key_for(key))
|
42
|
+
end
|
43
|
+
|
44
|
+
def acquired?(key)
|
45
|
+
@adapter.has_key?(key_for(key))
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def key_for(string)
|
51
|
+
[@prefix, "lock", string].compact.join('|')
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
require 'remote_lock/adapters/memcached'
|
57
|
+
require 'remote_lock/adapters/redis'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module RemoteLock::Adapters
|
4
|
+
class Base
|
5
|
+
|
6
|
+
def initialize(connection)
|
7
|
+
@connection = connection
|
8
|
+
end
|
9
|
+
|
10
|
+
def store(key, options = {})
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_key?(key, options = {})
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(key)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.valid?(adapter)
|
23
|
+
adapter.respond_to?(:store) &&
|
24
|
+
adapter.respond_to?(:has_key?) &&
|
25
|
+
adapter.respond_to?(:delete)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Globally unique ID for the current thread (or close enough)
|
31
|
+
def uid
|
32
|
+
"#{Socket.gethostname}-#{Process.pid}-#{thread_id}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def thread_id
|
36
|
+
Thread.current[:thread_uid] ||= SecureRandom.hex(4)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'remote_lock/adapters/base'
|
2
|
+
|
3
|
+
module RemoteLock::Adapters
|
4
|
+
class Memcached < Base
|
5
|
+
|
6
|
+
def store(key, expires_in_seconds)
|
7
|
+
status = @connection.add(key, uid, expires_in_seconds)
|
8
|
+
status =~ /^STORED/
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete(key)
|
12
|
+
@connection.delete(key)
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_key?(key)
|
16
|
+
@connection.get(key) == uid
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'remote_lock/adapters/base'
|
2
|
+
|
3
|
+
module RemoteLock::Adapters
|
4
|
+
class Redis < Base
|
5
|
+
|
6
|
+
def store(key, expires_in_seconds)
|
7
|
+
@connection.setnx(key, uid).tap do |status|
|
8
|
+
@connection.expire(key, expires_in_seconds) if status
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete(key)
|
13
|
+
@connection.del(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_key?(key)
|
17
|
+
@connection.get(key) == uid
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RemoteLock::Adapters
|
4
|
+
describe Memcached do
|
5
|
+
it_behaves_like 'a remote lock adapter', memcache
|
6
|
+
|
7
|
+
context "Memcache scope" do
|
8
|
+
let(:adapter) { Memcached.new(memcache) }
|
9
|
+
let(:uid) { '1234' }
|
10
|
+
let(:test_key) { "test_key" }
|
11
|
+
|
12
|
+
before do
|
13
|
+
adapter.stub(:uid).and_return(uid)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#store" do
|
17
|
+
it "should store the lock in memcached" do
|
18
|
+
memcache.get(test_key).should be_nil
|
19
|
+
adapter.store(test_key, 100)
|
20
|
+
memcache.get(test_key).should eq uid
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#has_key?" do
|
25
|
+
it "should return true if the key exists in memcache with uid value" do
|
26
|
+
memcache.add(test_key, uid)
|
27
|
+
adapter.has_key?(test_key).should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return false if the key doesn't exist in memcache or is a different uid" do
|
31
|
+
memcache.add(test_key, "notvalid")
|
32
|
+
adapter.has_key?(test_key).should be_false
|
33
|
+
memcache.delete(test_key)
|
34
|
+
adapter.has_key?(test_key).should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#delete" do
|
39
|
+
it "should remove the key from memcached" do
|
40
|
+
memcache.add(test_key, uid)
|
41
|
+
adapter.delete(test_key)
|
42
|
+
memcache.get(test_key).should be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RemoteLock::Adapters
|
4
|
+
describe Redis do
|
5
|
+
it_behaves_like 'a remote lock adapter', redis
|
6
|
+
|
7
|
+
context "Redis scope" do
|
8
|
+
let(:adapter) { Redis.new(redis) }
|
9
|
+
let(:uid) { '1234' }
|
10
|
+
let(:test_key) { "test_key" }
|
11
|
+
|
12
|
+
before do
|
13
|
+
adapter.stub(:uid).and_return(uid)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#store" do
|
17
|
+
it "should store the lock in memcached" do
|
18
|
+
redis.get(test_key).should be_nil
|
19
|
+
adapter.store(test_key, 100)
|
20
|
+
redis.get(test_key).should eq uid
|
21
|
+
end
|
22
|
+
|
23
|
+
context "expiry" do
|
24
|
+
it "should expire the key after the time is over" do
|
25
|
+
adapter.store(test_key, 1)
|
26
|
+
sleep 1.1
|
27
|
+
redis.exists(test_key).should be_false
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should expire the key after the time is over" do
|
31
|
+
adapter.store(test_key, 10)
|
32
|
+
sleep 0.5
|
33
|
+
redis.exists(test_key).should be_true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#has_key?" do
|
39
|
+
it "should return true if the key exists in memcache with uid value" do
|
40
|
+
redis.setnx(test_key, uid)
|
41
|
+
adapter.has_key?(test_key).should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return false if the key doesn't exist in memcache or is a different uid" do
|
45
|
+
redis.setnx(test_key, "notvalid")
|
46
|
+
adapter.has_key?(test_key).should be_false
|
47
|
+
redis.del(test_key)
|
48
|
+
adapter.has_key?(test_key).should be_false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#delete" do
|
53
|
+
it "should remove the key from memcached" do
|
54
|
+
redis.setnx(test_key, uid)
|
55
|
+
adapter.delete(test_key)
|
56
|
+
redis.get(test_key).should be_nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/memcache.yml
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RemoteLock do
|
4
|
+
|
5
|
+
adapters = {
|
6
|
+
:memcached => RemoteLock::Adapters::Memcached.new(memcache),
|
7
|
+
:redis => RemoteLock::Adapters::Redis.new(redis)
|
8
|
+
}
|
9
|
+
|
10
|
+
adapters.each_pair do |name, adapter|
|
11
|
+
context "Using adapter: #{name}" do
|
12
|
+
before do
|
13
|
+
Kernel.stub(:sleep)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:lock) { RemoteLock.new(adapter) }
|
17
|
+
|
18
|
+
describe "#synchronize" do
|
19
|
+
|
20
|
+
it "yields the block" do
|
21
|
+
expect { |call|
|
22
|
+
lock.synchronize('lock_key', &call)
|
23
|
+
}.to yield_control
|
24
|
+
end
|
25
|
+
|
26
|
+
it "acquires the specified lock before the block is run" do
|
27
|
+
adapter.has_key?("lock_key").should be_false
|
28
|
+
lock.synchronize('lock_key') do
|
29
|
+
adapter.has_key?("lock|lock_key").should be_true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "releases the lock after the block is run" do
|
34
|
+
adapter.has_key?("lock_key").should be_false
|
35
|
+
expect { |call| lock.synchronize('lock_key', &call) }.to yield_control
|
36
|
+
adapter.has_key?("lock|lock_key").should be_false
|
37
|
+
end
|
38
|
+
|
39
|
+
it "releases the lock even if the block raises" do
|
40
|
+
adapter.has_key?("lock|lock_key").should be_false
|
41
|
+
lock.synchronize('lock_key') { raise } rescue nil
|
42
|
+
adapter.has_key?("lock|lock_key").should be_false
|
43
|
+
end
|
44
|
+
|
45
|
+
specify "does not block on recursive lock acquisition" do
|
46
|
+
lock.synchronize('lock_key') do
|
47
|
+
lambda {
|
48
|
+
expect{ |call| lock.synchronize('lock_key', &call) }.to yield_control
|
49
|
+
}.should_not raise_error
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "permits recursive calls from the same thread" do
|
54
|
+
lock.acquire_lock('lock_key')
|
55
|
+
lambda {
|
56
|
+
expect { |call| lock.synchronize('lock_key', &call) }.to yield_control
|
57
|
+
}.should_not raise_error
|
58
|
+
end
|
59
|
+
|
60
|
+
it "prevents calls from different threads" do
|
61
|
+
lock.acquire_lock('lock_key')
|
62
|
+
another_thread do
|
63
|
+
lambda {
|
64
|
+
expect { |call| lock.synchronize('lock_key', &call) }.to_not yield_control
|
65
|
+
}.should raise_error(RemoteLock::Error)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#acquire_lock' do
|
71
|
+
specify "creates a lock at a given cache key" do
|
72
|
+
adapter.has_key?("lock|lock_key").should be_false
|
73
|
+
lock.acquire_lock("lock_key")
|
74
|
+
adapter.has_key?("lock|lock_key").should be_true
|
75
|
+
end
|
76
|
+
|
77
|
+
specify "retries specified number of times" do
|
78
|
+
lock.acquire_lock('lock_key')
|
79
|
+
another_process do
|
80
|
+
adapter.should_receive(:store).exactly(3).times.and_return(false)
|
81
|
+
lambda {
|
82
|
+
lock.acquire_lock('lock_key', :expiry => 10, :retries => 3)
|
83
|
+
}.should raise_error(RemoteLock::Error)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
specify "correctly sets timeout on entries" do
|
88
|
+
adapter.should_receive(:store).with('lock|lock_key', 42).and_return true
|
89
|
+
lock.acquire_lock('lock_key', :expiry => 42)
|
90
|
+
end
|
91
|
+
|
92
|
+
specify "prevents two processes from acquiring the same lock at the same time" do
|
93
|
+
lock.acquire_lock('lock_key')
|
94
|
+
another_process do
|
95
|
+
lambda { lock.acquire_lock('lock_key') }.should raise_error(RemoteLock::Error)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
specify "prevents two threads from acquiring the same lock at the same time" do
|
100
|
+
lock.acquire_lock('lock_key')
|
101
|
+
another_thread do
|
102
|
+
lambda { lock.acquire_lock('lock_key') }.should raise_error(RemoteLock::Error)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
specify "prevents a given thread from acquiring the same lock twice" do
|
107
|
+
lock.acquire_lock('lock_key')
|
108
|
+
lambda { lock.acquire_lock('lock_key') }.should raise_error(RemoteLock::Error)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#release_lock' do
|
113
|
+
specify "deletes the lock for a given cache key" do
|
114
|
+
adapter.has_key?("lock|lock_key").should be_false
|
115
|
+
lock.acquire_lock("lock_key")
|
116
|
+
adapter.has_key?("lock|lock_key").should be_true
|
117
|
+
lock.release_lock("lock_key")
|
118
|
+
adapter.has_key?("lock|lock_key").should be_false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "lock prefixing" do
|
123
|
+
it "should prefix the key name when a prefix is set" do
|
124
|
+
lock = RemoteLock.new(adapter, "staging_server")
|
125
|
+
lock.acquire_lock("lock_key")
|
126
|
+
adapter.has_key?("staging_server|lock|lock_key").should be_true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# helpers
|
133
|
+
|
134
|
+
def another_process
|
135
|
+
current_pid = Process.pid
|
136
|
+
Process.stub :pid => (current_pid + 1)
|
137
|
+
redis.client.reconnect
|
138
|
+
yield
|
139
|
+
Process.unstub :pid
|
140
|
+
redis.client.reconnect
|
141
|
+
end
|
142
|
+
|
143
|
+
def another_thread
|
144
|
+
old_tid = Thread.current[:thread_uid]
|
145
|
+
Thread.current[:thread_uid] = nil
|
146
|
+
yield
|
147
|
+
Thread.current[:thread_uid] = old_tid
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'remote_lock'
|
4
|
+
require 'memcache'
|
5
|
+
require 'redis'
|
6
|
+
|
7
|
+
require "rspec"
|
8
|
+
require "rspec/core"
|
9
|
+
require 'rspec/core/rake_task'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'support/**/*.rb')).each do |file|
|
13
|
+
require file
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.before :each do
|
18
|
+
memcache.flush_all
|
19
|
+
redis.flushdb
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
shared_examples_for "a remote lock adapter" do |storage|
|
2
|
+
let(:adapter) { described_class.new(storage) }
|
3
|
+
let(:key) { 'my_test_key' }
|
4
|
+
|
5
|
+
it "should behave like RemoteLock::Strategies::Base" do
|
6
|
+
RemoteLock::Adapters::Base.valid?(adapter).should be_true
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
describe "#store" do
|
11
|
+
it "should set the key as acquired" do
|
12
|
+
adapter.has_key?(key).should be_false
|
13
|
+
adapter.store(key, 100)
|
14
|
+
adapter.has_key?(key).should be_true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#delete" do
|
19
|
+
it "should remove the key as locked" do
|
20
|
+
adapter.store(key, 100)
|
21
|
+
adapter.has_key?(key).should be_true
|
22
|
+
adapter.delete(key)
|
23
|
+
adapter.has_key?(key).should be_false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: remote_lock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Julien Letessier
|
9
|
+
- Tiago Scolari
|
10
|
+
- Arne Hartherz
|
11
|
+
- Pedro Cunha
|
12
|
+
- Khiet Le
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
date: 2013-11-08 00:00:00.000000000 Z
|
17
|
+
dependencies:
|
18
|
+
- !ruby/object:Gem::Dependency
|
19
|
+
name: rake
|
20
|
+
requirement: !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ! '>='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
type: :development
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: pry
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
type: :development
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: pry-nav
|
52
|
+
requirement: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
type: :development
|
59
|
+
prerelease: false
|
60
|
+
version_requirements: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: rspec
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: rdoc
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: redis
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
type: :development
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: memcache-client
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
description: remote-based mutexes
|
131
|
+
email:
|
132
|
+
- julien.letessier@gmail.com
|
133
|
+
- tscolari@gmail.com
|
134
|
+
- arne.hartherz@makandra.de
|
135
|
+
- pkunha@gmail.com
|
136
|
+
- kle@housetrip.com
|
137
|
+
executables: []
|
138
|
+
extensions: []
|
139
|
+
extra_rdoc_files: []
|
140
|
+
files:
|
141
|
+
- lib/remote_lock/adapters/base.rb
|
142
|
+
- lib/remote_lock/adapters/memcached.rb
|
143
|
+
- lib/remote_lock/adapters/redis.rb
|
144
|
+
- lib/remote_lock/version.rb
|
145
|
+
- lib/remote_lock.rb
|
146
|
+
- LICENSE
|
147
|
+
- Rakefile
|
148
|
+
- README.md
|
149
|
+
- spec/adapters/hash_spec.rb
|
150
|
+
- spec/adapters/memcached_spec.rb
|
151
|
+
- spec/adapters/redis_spec.rb
|
152
|
+
- spec/memcache.yml
|
153
|
+
- spec/remote_lock_spec.rb
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
- spec/support/behave_as_a_remote_lock_adapter.rb
|
156
|
+
- spec/support/memcached_storage.rb
|
157
|
+
- spec/support/redis_storage.rb
|
158
|
+
homepage: http://github.com/HouseTrip/remote_lock
|
159
|
+
licenses:
|
160
|
+
- MIT
|
161
|
+
post_install_message:
|
162
|
+
rdoc_options: []
|
163
|
+
require_paths:
|
164
|
+
- lib
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
166
|
+
none: false
|
167
|
+
requirements:
|
168
|
+
- - ! '>='
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0'
|
171
|
+
segments:
|
172
|
+
- 0
|
173
|
+
hash: 589636169546274691
|
174
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
|
+
none: false
|
176
|
+
requirements:
|
177
|
+
- - ! '>='
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
segments:
|
181
|
+
- 0
|
182
|
+
hash: 589636169546274691
|
183
|
+
requirements: []
|
184
|
+
rubyforge_project:
|
185
|
+
rubygems_version: 1.8.23
|
186
|
+
signing_key:
|
187
|
+
specification_version: 3
|
188
|
+
summary: Leverages (memcached|redis)'s atomic operation to provide a distributed locking
|
189
|
+
/ synchromisation mechanism.
|
190
|
+
test_files:
|
191
|
+
- spec/adapters/hash_spec.rb
|
192
|
+
- spec/adapters/memcached_spec.rb
|
193
|
+
- spec/adapters/redis_spec.rb
|
194
|
+
- spec/memcache.yml
|
195
|
+
- spec/remote_lock_spec.rb
|
196
|
+
- spec/spec_helper.rb
|
197
|
+
- spec/support/behave_as_a_remote_lock_adapter.rb
|
198
|
+
- spec/support/memcached_storage.rb
|
199
|
+
- spec/support/redis_storage.rb
|