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 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