dynamodb-mutex 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rst CHANGED
@@ -15,14 +15,16 @@ Usage
15
15
  # Access to shared resource.
16
16
  end
17
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
-
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.
@@ -1,2 +1 @@
1
-
2
- require_relative 'dynamodb_mutex'
1
+ require_relative 'dynamodb_mutex'
@@ -1,8 +1,7 @@
1
-
2
1
  require 'dynamodb_mutex/lock'
3
2
 
4
3
  module DynamoDBMutex
5
-
4
+
6
5
  def self.with_lock *args, &block
7
6
  Lock.with_lock(*args, &block)
8
7
  end
@@ -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[:ttl] ||= 10
16
- opts[:block] ||= 1
17
- opts[:sleep] ||= 0.1
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[:ttl]) { return(yield) }
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 hold #{name} after #{opts[:block]} ms"
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[:block]
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 expired?(name, opts[:ttl])
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 "ACQUIRED LOCK #{name} by #{pid}"
38
+ logger.info "#{pid} acquired #{name}"
39
39
  return true
40
40
  rescue AWS::DynamoDB::Errors::ConditionalCheckFailedException
41
- logger.info "Waiting for the #{name} (pid=#{pid})..."
42
- sleep opts[:sleep]
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 "RELEASED LOCK #{name} by #{pid}"
52
+ logger.info "#{pid} released lock #{name}"
52
53
  end
53
54
 
54
55
  def pid
55
- Process.pid
56
+ @hostname ||= Socket.gethostname
57
+
58
+ "#{@hostname}-#{Process.pid}"
56
59
  end
57
60
 
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)
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
- logger.info "table named: #{TABLE_NAME} already exists"
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
- @table = dynamo_db.tables.create(TABLE_NAME, 5, 5, {})
77
- sleep 1 unless @table.status == :active
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
@@ -1,4 +1,4 @@
1
1
 
2
2
  module DynamoDBMutex
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
4
4
  end
@@ -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 'test.lock' do
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('test.lock'){ sleep 1 }
52
+ locker.with_lock(lockname, wait_for_other: 0, stale_after: 100) { return }
29
53
  }.to raise_error(DynamoDBMutex::LockError)
30
- Process.waitall
31
- else
32
- run(1, 5)
54
+
55
+ ensure
56
+ Process.kill('QUIT', child)
33
57
  end
34
58
  end
35
59
 
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)
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.1
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-08 00:00:00.000000000 Z
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 coordinate
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
- - spec/dynamodb_mutex_spec.rb
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: 2.4.4
131
+ rubygems_version: 1.8.23.2
119
132
  signing_key:
120
- specification_version: 4
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