dynamodb-mutex 0.0.1 → 0.0.2
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/README.rst +13 -11
- data/lib/dynamodb-mutex.rb +1 -2
- data/lib/dynamodb_mutex.rb +1 -2
- data/lib/dynamodb_mutex/lock.rb +33 -22
- data/lib/dynamodb_mutex/version.rb +1 -1
- data/spec/dynamodb_mutex_spec.rb +55 -27
- data/spec/spec_helper.rb +7 -2
- metadata +34 -21
- checksums.yaml +0 -7
data/README.rst
CHANGED
@@ -15,14 +15,16 @@ Usage
|
|
15
15
|
# Access to shared resource.
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
18
|
+
If no lock name (``:your_lock`` above) is given, ``#with_lock`` uses
|
19
|
+
``'default.lock'``.
|
20
|
+
|
21
|
+
You can pass ``with_lock`` the following options:
|
22
|
+
|
23
|
+
* ``:wait_for_other`` (default ``1``):
|
24
|
+
Seconds to to wait for another process to release the lock.
|
25
|
+
* ``:polling_interval`` (default ``0.1``):
|
26
|
+
Seconds between retrials to acquire lock. Should be at least
|
27
|
+
"``(:wait_for_other / 5) * (no_of_instances - 1)``".
|
28
|
+
* ``:stale_after`` (default ``10``):
|
29
|
+
Seconds after which the lock is considered stale and will be automatically
|
30
|
+
deleted; set to "falsey" (``nil`` or ``false``) to disable.
|
data/lib/dynamodb-mutex.rb
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
require_relative 'dynamodb_mutex'
|
1
|
+
require_relative 'dynamodb_mutex'
|
data/lib/dynamodb_mutex.rb
CHANGED
data/lib/dynamodb_mutex/lock.rb
CHANGED
@@ -4,61 +4,66 @@ require_relative 'logging'
|
|
4
4
|
module DynamoDBMutex
|
5
5
|
|
6
6
|
LockError = Class.new(StandardError)
|
7
|
-
|
7
|
+
|
8
8
|
module Lock
|
9
9
|
include Logging
|
10
10
|
extend self
|
11
11
|
|
12
12
|
TABLE_NAME = 'dynamodb-mutex'
|
13
13
|
|
14
|
-
def with_lock name, opts = {}
|
15
|
-
opts[:
|
16
|
-
opts[:
|
17
|
-
opts[:
|
14
|
+
def with_lock name = 'default.lock', opts = {}
|
15
|
+
opts[:stale_after] ||= 10 # seconds
|
16
|
+
opts[:wait_for_other] ||= 1 # seconds
|
17
|
+
opts[:polling_interval] ||= 0.1 # seconds
|
18
18
|
|
19
19
|
if create(name, opts)
|
20
|
-
begin Timeout::timeout(opts[:
|
20
|
+
begin Timeout::timeout(opts[:stale_after]) { return(yield) }
|
21
21
|
ensure delete(name)
|
22
22
|
end
|
23
23
|
else
|
24
|
-
raise LockError, "Unable to
|
24
|
+
raise LockError, "Unable to acquire #{name} after #{opts[:wait_for_other]} seconds"
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def create name, opts
|
31
|
-
acquire_timeout = Time.now.to_i + opts[:
|
31
|
+
acquire_timeout = Time.now.to_i + opts[:wait_for_other]
|
32
32
|
|
33
33
|
while Time.now.to_i < acquire_timeout
|
34
|
-
delete(name) if
|
34
|
+
delete(name) if stale?(name, opts[:stale_after])
|
35
35
|
begin
|
36
36
|
table.items.put({:id => name, :created => Time.now.to_i},
|
37
37
|
:unless_exists => :id)
|
38
|
-
logger.info "
|
38
|
+
logger.info "#{pid} acquired #{name}"
|
39
39
|
return true
|
40
40
|
rescue AWS::DynamoDB::Errors::ConditionalCheckFailedException
|
41
|
-
logger.info "
|
42
|
-
sleep opts[:
|
41
|
+
logger.info "#{pid} is waiting for #{name}"
|
42
|
+
sleep opts[:polling_interval]
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
+
logger.warn "#{pid} failed to acquire #{name}"
|
46
47
|
false
|
47
48
|
end
|
48
49
|
|
49
50
|
def delete(name)
|
50
51
|
table.items.at(name).delete
|
51
|
-
logger.info "
|
52
|
+
logger.info "#{pid} released lock #{name}"
|
52
53
|
end
|
53
54
|
|
54
55
|
def pid
|
55
|
-
|
56
|
+
@hostname ||= Socket.gethostname
|
57
|
+
|
58
|
+
"#{@hostname}-#{Process.pid}"
|
56
59
|
end
|
57
60
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
61
|
+
def stale?(name, ttl)
|
62
|
+
return false unless ttl
|
63
|
+
|
64
|
+
if lock_attributes = table.items.at(name).attributes.to_h(:consistent_read => true)
|
65
|
+
if time_locked = lock_attributes["created"]
|
66
|
+
time_locked < (Time.now.to_i - ttl)
|
62
67
|
end
|
63
68
|
end
|
64
69
|
end
|
@@ -68,16 +73,22 @@ module DynamoDBMutex
|
|
68
73
|
dynamo_db = AWS::DynamoDB.new
|
69
74
|
|
70
75
|
begin
|
76
|
+
tries ||= 10
|
71
77
|
@table = dynamo_db.tables[TABLE_NAME].load_schema
|
72
78
|
rescue AWS::DynamoDB::Errors::ResourceInUseException
|
73
|
-
|
79
|
+
raise LockError, "Cannot load schema for table #{TABLE_NAME}" if (tries -= 1).zero?
|
80
|
+
|
81
|
+
logger.info "Could not load schema for table #{TABLE_NAME}; retrying"
|
82
|
+
sleep 1
|
74
83
|
retry
|
75
84
|
rescue AWS::DynamoDB::Errors::ResourceNotFoundException
|
76
|
-
|
77
|
-
|
85
|
+
logger.info "Creating table #{TABLE_NAME}"
|
86
|
+
@table = dynamo_db.tables.create(TABLE_NAME, 5, 5)
|
87
|
+
sleep 1 while @table.status != :active
|
88
|
+
logger.info "Table #{TABLE_NAME} created"
|
78
89
|
end
|
79
90
|
|
80
91
|
@table
|
81
92
|
end
|
82
93
|
end
|
83
|
-
end
|
94
|
+
end
|
data/spec/dynamodb_mutex_spec.rb
CHANGED
@@ -1,47 +1,75 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
describe DynamoDBMutex::Lock do
|
4
|
-
|
5
|
+
|
5
6
|
let(:locker) { DynamoDBMutex::Lock }
|
7
|
+
let(:lockname) { 'test.lock' }
|
6
8
|
|
7
9
|
describe '#with_lock' do
|
8
|
-
|
9
|
-
def run(id, ms)
|
10
|
-
print "invoked worker #{id}...\n"
|
11
|
-
locker.with_lock 'test.lock' do
|
12
|
-
sleep(ms)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'should execute block by default' do
|
10
|
+
it 'executes block' do
|
17
11
|
locked = false
|
18
|
-
locker.with_lock
|
12
|
+
locker.with_lock(lockname) do
|
19
13
|
locked = true
|
20
14
|
end
|
21
15
|
expect(locked).to eq(true)
|
22
16
|
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#with_lock', pipe: true do
|
20
|
+
before(:example) do
|
21
|
+
@notify = Class.new do
|
22
|
+
def initialize
|
23
|
+
@reader, @writer = IO.pipe
|
24
|
+
end
|
25
|
+
|
26
|
+
def poke
|
27
|
+
@reader.close
|
28
|
+
@writer.write "\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
def wait
|
32
|
+
@writer.close
|
33
|
+
@reader.gets
|
34
|
+
end
|
35
|
+
end.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def run_infinitely(notify)
|
39
|
+
locker.with_lock(lockname) do
|
40
|
+
notify.poke
|
41
|
+
sleep
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'raises error after :wait_for_other timeout' do
|
46
|
+
begin
|
47
|
+
child = fork { run_infinitely(@notify) }
|
48
|
+
|
49
|
+
@notify.wait
|
23
50
|
|
24
|
-
it 'should raise error after block timeout' do
|
25
|
-
if pid1 = fork
|
26
|
-
sleep(1)
|
27
51
|
expect {
|
28
|
-
locker.with_lock(
|
52
|
+
locker.with_lock(lockname, wait_for_other: 0, stale_after: 100) { return }
|
29
53
|
}.to raise_error(DynamoDBMutex::LockError)
|
30
|
-
|
31
|
-
|
32
|
-
|
54
|
+
|
55
|
+
ensure
|
56
|
+
Process.kill('QUIT', child)
|
33
57
|
end
|
34
58
|
end
|
35
59
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
60
|
+
|
61
|
+
it 'deletes lock if stale' do
|
62
|
+
begin
|
63
|
+
child = fork { run_infinitely(@notify) }
|
64
|
+
|
65
|
+
@notify.wait
|
66
|
+
|
67
|
+
locker.with_lock(lockname, stale_after: 0, wait_for_other: 100) do
|
68
|
+
expect(locker).to receive(:delete).with(lockname)
|
69
|
+
end
|
70
|
+
|
71
|
+
ensure
|
72
|
+
Process.kill('QUIT', child)
|
45
73
|
end
|
46
74
|
end
|
47
75
|
|
data/spec/spec_helper.rb
CHANGED
@@ -6,11 +6,15 @@ require 'aws-sdk'
|
|
6
6
|
|
7
7
|
AWS.config(:use_ssl => false,
|
8
8
|
:dynamo_db_endpoint => 'localhost',
|
9
|
-
:dynamo_db_port => 4567
|
9
|
+
:dynamo_db_port => 4567,
|
10
|
+
:access_key_id => 'foo',
|
11
|
+
:secret_access_key => 'bar',
|
12
|
+
)
|
10
13
|
|
11
14
|
require 'dynamodb-mutex'
|
12
15
|
|
13
16
|
RSpec.configure do |config|
|
17
|
+
DynamoDBMutex::Lock.logger = Logger.new(StringIO.new, 1024*1024, 10)
|
14
18
|
|
15
19
|
dynamo_thread = nil
|
16
20
|
|
@@ -19,6 +23,7 @@ RSpec.configure do |config|
|
|
19
23
|
FakeDynamo::Storage.instance.load_aof
|
20
24
|
|
21
25
|
dynamo_thread = Thread.new do
|
26
|
+
$stdout = StringIO.new
|
22
27
|
FakeDynamo::Server.run!(port: 4567, bind: 'localhost') do |server|
|
23
28
|
if server.respond_to?('config') && server.config.respond_to?('[]=')
|
24
29
|
server.config[:AccessLog] = []
|
@@ -34,4 +39,4 @@ RSpec.configure do |config|
|
|
34
39
|
FileUtils.rm('test.fdb', force: true)
|
35
40
|
end
|
36
41
|
|
37
|
-
end
|
42
|
+
end
|
metadata
CHANGED
@@ -1,74 +1,84 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynamodb-mutex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Dinesh Yadav
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
12
|
+
date: 2014-12-10 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: aws-sdk
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- -
|
19
|
+
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: '0'
|
20
22
|
type: :runtime
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - ! '>='
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '0'
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: rspec
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
|
-
- -
|
35
|
+
- - ! '>='
|
32
36
|
- !ruby/object:Gem::Version
|
33
37
|
version: 2.0.0
|
34
38
|
type: :development
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
|
-
- -
|
43
|
+
- - ! '>='
|
39
44
|
- !ruby/object:Gem::Version
|
40
45
|
version: 2.0.0
|
41
46
|
- !ruby/object:Gem::Dependency
|
42
47
|
name: rspec-mocks
|
43
48
|
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
44
50
|
requirements:
|
45
|
-
- -
|
51
|
+
- - ! '>='
|
46
52
|
- !ruby/object:Gem::Version
|
47
53
|
version: '0'
|
48
54
|
type: :development
|
49
55
|
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
51
58
|
requirements:
|
52
|
-
- -
|
59
|
+
- - ! '>='
|
53
60
|
- !ruby/object:Gem::Version
|
54
61
|
version: '0'
|
55
62
|
- !ruby/object:Gem::Dependency
|
56
63
|
name: rake
|
57
64
|
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
58
66
|
requirements:
|
59
|
-
- -
|
67
|
+
- - ! '>='
|
60
68
|
- !ruby/object:Gem::Version
|
61
69
|
version: '0'
|
62
70
|
type: :development
|
63
71
|
prerelease: false
|
64
72
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
65
74
|
requirements:
|
66
|
-
- -
|
75
|
+
- - ! '>='
|
67
76
|
- !ruby/object:Gem::Version
|
68
77
|
version: '0'
|
69
78
|
- !ruby/object:Gem::Dependency
|
70
79
|
name: fake_dynamo
|
71
80
|
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
72
82
|
requirements:
|
73
83
|
- - '='
|
74
84
|
- !ruby/object:Gem::Version
|
@@ -76,47 +86,50 @@ dependencies:
|
|
76
86
|
type: :development
|
77
87
|
prerelease: false
|
78
88
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
79
90
|
requirements:
|
80
91
|
- - '='
|
81
92
|
- !ruby/object:Gem::Version
|
82
93
|
version: 0.1.3
|
83
|
-
description: dynamodb-mutex implements a simple mutex that can be used to
|
94
|
+
description: dynamodb-mutex implements a simple mutex that can be used to coordinateaccess
|
95
|
+
to shared data from multiple concurrent hosts
|
84
96
|
email: dy@clearhaus.com
|
85
97
|
executables: []
|
86
98
|
extensions: []
|
87
99
|
extra_rdoc_files: []
|
88
100
|
files:
|
89
|
-
- README.rst
|
90
101
|
- Rakefile
|
91
|
-
- lib/dynamodb-mutex.rb
|
92
|
-
- lib/dynamodb_mutex.rb
|
93
|
-
- lib/dynamodb_mutex/lock.rb
|
94
102
|
- lib/dynamodb_mutex/logging.rb
|
103
|
+
- lib/dynamodb_mutex/lock.rb
|
95
104
|
- lib/dynamodb_mutex/version.rb
|
96
|
-
-
|
105
|
+
- lib/dynamodb-mutex.rb
|
106
|
+
- lib/dynamodb_mutex.rb
|
97
107
|
- spec/spec_helper.rb
|
108
|
+
- spec/dynamodb_mutex_spec.rb
|
109
|
+
- README.rst
|
98
110
|
homepage: http://github.com/clearhaus/dynamodb-mutex
|
99
111
|
licenses:
|
100
112
|
- MIT
|
101
|
-
metadata: {}
|
102
113
|
post_install_message:
|
103
114
|
rdoc_options: []
|
104
115
|
require_paths:
|
105
116
|
- lib
|
106
117
|
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
107
119
|
requirements:
|
108
|
-
- -
|
120
|
+
- - ! '>='
|
109
121
|
- !ruby/object:Gem::Version
|
110
122
|
version: '0'
|
111
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
112
125
|
requirements:
|
113
|
-
- -
|
126
|
+
- - ! '>='
|
114
127
|
- !ruby/object:Gem::Version
|
115
128
|
version: '0'
|
116
129
|
requirements: []
|
117
130
|
rubyforge_project:
|
118
|
-
rubygems_version:
|
131
|
+
rubygems_version: 1.8.23.2
|
119
132
|
signing_key:
|
120
|
-
specification_version:
|
133
|
+
specification_version: 3
|
121
134
|
summary: Distributed mutex based on AWS DynamoDB
|
122
135
|
test_files: []
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: 2c51332da52855eb26f2f6dcf190ca6c46da6f75
|
4
|
-
data.tar.gz: 795579cb3f014ac3974cc30d90658f917ff5cef8
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: ca601a979c69633c6d21ff8e40c614b482936134fe5aab758cb1107aa24ccb934a106bb015715533fb255e4de13836743ff7f0fd62b7aef42036e94ed6219b8b
|
7
|
-
data.tar.gz: b7ba8b59b985ff8b067e88b621f81859bb963475c0c92ca0031cc246f5abc9839083704298d9154d24054ce980c9993eb20fbe47b709afdbe3fc53533a103bc0
|