dynamodb-mutex 0.0.1
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.
- checksums.yaml +7 -0
- data/README.rst +28 -0
- data/Rakefile +7 -0
- data/lib/dynamodb-mutex.rb +2 -0
- data/lib/dynamodb_mutex/lock.rb +83 -0
- data/lib/dynamodb_mutex/logging.rb +14 -0
- data/lib/dynamodb_mutex/version.rb +4 -0
- data/lib/dynamodb_mutex.rb +10 -0
- data/spec/dynamodb_mutex_spec.rb +49 -0
- data/spec/spec_helper.rb +37 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c51332da52855eb26f2f6dcf190ca6c46da6f75
|
4
|
+
data.tar.gz: 795579cb3f014ac3974cc30d90658f917ff5cef8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ca601a979c69633c6d21ff8e40c614b482936134fe5aab758cb1107aa24ccb934a106bb015715533fb255e4de13836743ff7f0fd62b7aef42036e94ed6219b8b
|
7
|
+
data.tar.gz: b7ba8b59b985ff8b067e88b621f81859bb963475c0c92ca0031cc246f5abc9839083704298d9154d24054ce980c9993eb20fbe47b709afdbe3fc53533a103bc0
|
data/README.rst
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
DynamoDB Mutex
|
2
|
+
==============
|
3
|
+
|
4
|
+
Using DynamoDB, it implements a simple semaphore that can be used to coordinate
|
5
|
+
access to shared data from multiple concurrent hosts or processes.
|
6
|
+
|
7
|
+
Usage
|
8
|
+
-----
|
9
|
+
|
10
|
+
.. code-block:: ruby
|
11
|
+
|
12
|
+
require 'dynamodb-mutex'
|
13
|
+
|
14
|
+
DynamoDBMutex.with_lock :your_lock do
|
15
|
+
# Access to shared resource.
|
16
|
+
end
|
17
|
+
|
18
|
+
You can pass following options to ``with_lock``
|
19
|
+
|
20
|
+
.. code-block:: ruby
|
21
|
+
|
22
|
+
:block => 1 # Specify in seconds how long you want to wait for the lock to be released. (default: 1)
|
23
|
+
# It will raise DynamoDBMutex::LockError after block timeout
|
24
|
+
:sleep => 0.1 # Specify in seconds how long the polling interval should be when :block is given.
|
25
|
+
# It is NOT recommended to go below 0.01. (default: 0.1)
|
26
|
+
:ttl => 10 # Specify in seconds when the lock should be considered stale when something went wrong
|
27
|
+
# with the one who held the lock and failed to unlock. (default: 10)
|
28
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative 'logging'
|
3
|
+
|
4
|
+
module DynamoDBMutex
|
5
|
+
|
6
|
+
LockError = Class.new(StandardError)
|
7
|
+
|
8
|
+
module Lock
|
9
|
+
include Logging
|
10
|
+
extend self
|
11
|
+
|
12
|
+
TABLE_NAME = 'dynamodb-mutex'
|
13
|
+
|
14
|
+
def with_lock name, opts = {}
|
15
|
+
opts[:ttl] ||= 10
|
16
|
+
opts[:block] ||= 1
|
17
|
+
opts[:sleep] ||= 0.1
|
18
|
+
|
19
|
+
if create(name, opts)
|
20
|
+
begin Timeout::timeout(opts[:ttl]) { return(yield) }
|
21
|
+
ensure delete(name)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
raise LockError, "Unable to hold #{name} after #{opts[:block]} ms"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def create name, opts
|
31
|
+
acquire_timeout = Time.now.to_i + opts[:block]
|
32
|
+
|
33
|
+
while Time.now.to_i < acquire_timeout
|
34
|
+
delete(name) if expired?(name, opts[:ttl])
|
35
|
+
begin
|
36
|
+
table.items.put({:id => name, :created => Time.now.to_i},
|
37
|
+
:unless_exists => :id)
|
38
|
+
logger.info "ACQUIRED LOCK #{name} by #{pid}"
|
39
|
+
return true
|
40
|
+
rescue AWS::DynamoDB::Errors::ConditionalCheckFailedException
|
41
|
+
logger.info "Waiting for the #{name} (pid=#{pid})..."
|
42
|
+
sleep opts[:sleep]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete(name)
|
50
|
+
table.items.at(name).delete
|
51
|
+
logger.info "RELEASED LOCK #{name} by #{pid}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def pid
|
55
|
+
Process.pid
|
56
|
+
end
|
57
|
+
|
58
|
+
def expired?(name, ttl)
|
59
|
+
if l = table.items.at(name).attributes.to_h(:consistent_read => true)
|
60
|
+
if t = l["created"]
|
61
|
+
t < (Time.now.to_i - ttl)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def table
|
67
|
+
return @table if @table
|
68
|
+
dynamo_db = AWS::DynamoDB.new
|
69
|
+
|
70
|
+
begin
|
71
|
+
@table = dynamo_db.tables[TABLE_NAME].load_schema
|
72
|
+
rescue AWS::DynamoDB::Errors::ResourceInUseException
|
73
|
+
logger.info "table named: #{TABLE_NAME} already exists"
|
74
|
+
retry
|
75
|
+
rescue AWS::DynamoDB::Errors::ResourceNotFoundException
|
76
|
+
@table = dynamo_db.tables.create(TABLE_NAME, 5, 5, {})
|
77
|
+
sleep 1 unless @table.status == :active
|
78
|
+
end
|
79
|
+
|
80
|
+
@table
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DynamoDBMutex::Lock do
|
4
|
+
|
5
|
+
let(:locker) { DynamoDBMutex::Lock }
|
6
|
+
|
7
|
+
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
|
17
|
+
locked = false
|
18
|
+
locker.with_lock 'test.lock' do
|
19
|
+
locked = true
|
20
|
+
end
|
21
|
+
expect(locked).to eq(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should raise error after block timeout' do
|
25
|
+
if pid1 = fork
|
26
|
+
sleep(1)
|
27
|
+
expect {
|
28
|
+
locker.with_lock('test.lock'){ sleep 1 }
|
29
|
+
}.to raise_error(DynamoDBMutex::LockError)
|
30
|
+
Process.waitall
|
31
|
+
else
|
32
|
+
run(1, 5)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should expire lock if stale' do
|
37
|
+
if pid1 = fork
|
38
|
+
sleep(2)
|
39
|
+
locker.with_lock 'test.lock', block: 10 do
|
40
|
+
expect(locker).to receive(:delete).with('test.lock')
|
41
|
+
end
|
42
|
+
Process.waitall
|
43
|
+
else
|
44
|
+
run(1, 5)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rspec'
|
4
|
+
require 'fake_dynamo'
|
5
|
+
require 'aws-sdk'
|
6
|
+
|
7
|
+
AWS.config(:use_ssl => false,
|
8
|
+
:dynamo_db_endpoint => 'localhost',
|
9
|
+
:dynamo_db_port => 4567)
|
10
|
+
|
11
|
+
require 'dynamodb-mutex'
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
|
15
|
+
dynamo_thread = nil
|
16
|
+
|
17
|
+
config.before(:suite) do
|
18
|
+
FakeDynamo::Storage.db_path = 'test.fdb'
|
19
|
+
FakeDynamo::Storage.instance.load_aof
|
20
|
+
|
21
|
+
dynamo_thread = Thread.new do
|
22
|
+
FakeDynamo::Server.run!(port: 4567, bind: 'localhost') do |server|
|
23
|
+
if server.respond_to?('config') && server.config.respond_to?('[]=')
|
24
|
+
server.config[:AccessLog] = []
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
sleep(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
config.after(:suite) do
|
32
|
+
FakeDynamo::Storage.instance.shutdown
|
33
|
+
dynamo_thread.exit if dynamo_thread
|
34
|
+
FileUtils.rm('test.fdb', force: true)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dynamodb-mutex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dinesh Yadav
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.0.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-mocks
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: fake_dynamo
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.1.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.1.3
|
83
|
+
description: dynamodb-mutex implements a simple mutex that can be used to coordinate
|
84
|
+
email: dy@clearhaus.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- README.rst
|
90
|
+
- Rakefile
|
91
|
+
- lib/dynamodb-mutex.rb
|
92
|
+
- lib/dynamodb_mutex.rb
|
93
|
+
- lib/dynamodb_mutex/lock.rb
|
94
|
+
- lib/dynamodb_mutex/logging.rb
|
95
|
+
- lib/dynamodb_mutex/version.rb
|
96
|
+
- spec/dynamodb_mutex_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
homepage: http://github.com/clearhaus/dynamodb-mutex
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.4.4
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Distributed mutex based on AWS DynamoDB
|
122
|
+
test_files: []
|