dynamodb-mutex 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ # RSpec
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new('spec')
7
+ task :default => :spec
@@ -0,0 +1,2 @@
1
+
2
+ require_relative 'dynamodb_mutex'
@@ -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,14 @@
1
+ require 'logger'
2
+
3
+ module Logging
4
+ attr_writer :logger
5
+
6
+ def logger
7
+ @logger ||= lambda {
8
+ logger = Logger.new(STDOUT, 1024*1024, 10)
9
+ logger.level = Logger::DEBUG
10
+ logger
11
+ }.call
12
+ end
13
+
14
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module DynamoDBMutex
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,10 @@
1
+
2
+ require 'dynamodb_mutex/lock'
3
+
4
+ module DynamoDBMutex
5
+
6
+ def self.with_lock *args, &block
7
+ Lock.with_lock(*args, &block)
8
+ end
9
+
10
+ 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
@@ -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: []