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 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: []