sdb_lock 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.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +71 -0
- data/Rakefile +2 -0
- data/lib/sdb_lock/version.rb +3 -0
- data/lib/sdb_lock.rb +154 -0
- data/sdb_lock.gemspec +21 -0
- metadata +91 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 KAWACHI Takashi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# SdbLock
|
2
|
+
|
3
|
+
Poor man's distributed lock using SimpleDB. It is useful when you don't want to
|
4
|
+
maintain distributed lock server by yourself.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'sdb_lock'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install sdb_lock
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
````
|
23
|
+
require 'sdb_lock'
|
24
|
+
|
25
|
+
lock = SdbLock.new(
|
26
|
+
'my_app_lock_domain', # SimpleDB domain name to use
|
27
|
+
create_domain: true, # At the first time, you will need to create domain. Note it might take long time.
|
28
|
+
|
29
|
+
# Other hash members will be passed to AWS::SimpleDB#new as is.
|
30
|
+
# You can set credential by other ways including environmental variables.
|
31
|
+
# See https://github.com/amazonwebservices/aws-sdk-for-ruby
|
32
|
+
|
33
|
+
# see http://docs.amazonwebservices.com/general/latest/gr/rande.html#sdb_region
|
34
|
+
simple_db_endpoint: "sdb.ap-northeast-1.amazonaws.com",
|
35
|
+
access_key_id: YOUR_AWS_ACCESS_KEY,
|
36
|
+
secret_access_key: YOUR_AWS_SECRET
|
37
|
+
)
|
38
|
+
|
39
|
+
locked = lock.try_lock("a1") do
|
40
|
+
# do some work
|
41
|
+
end
|
42
|
+
|
43
|
+
# if you want to block until gain lock, then
|
44
|
+
lock.lock("a1") do
|
45
|
+
# do some work
|
46
|
+
end
|
47
|
+
|
48
|
+
# List locked resource names
|
49
|
+
lock.locked_resources
|
50
|
+
|
51
|
+
# Some times lock might remain because of network failure. Then we'll need
|
52
|
+
# a way to unlock these.
|
53
|
+
#
|
54
|
+
# Unlock older than 10 secs.
|
55
|
+
lock.unlock_old(10)
|
56
|
+
|
57
|
+
````
|
58
|
+
|
59
|
+
## Limitation
|
60
|
+
|
61
|
+
* Lock might remain by network failure or other reason. See `#unlock_old`.
|
62
|
+
* Number of domains are limited by SimpleDB.
|
63
|
+
* Each Lock is represented by an item in SimpleDB. Number of items are limited by SimpleDB.
|
64
|
+
|
65
|
+
## Contributing
|
66
|
+
|
67
|
+
1. Fork it
|
68
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
69
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
70
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
71
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/sdb_lock.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
require "sdb_lock/version"
|
2
|
+
|
3
|
+
require 'aws'
|
4
|
+
|
5
|
+
# Lock using SimpleDB conditional put.
|
6
|
+
#
|
7
|
+
# Create instance.
|
8
|
+
# lock = SdbLock.new('my_app_lock', access_key_id: YOUR_AWS_ACCESS_KEY, secret_access_key: YOUR_AWS_SECRET)
|
9
|
+
#
|
10
|
+
# Or if you set up AWS account in another way.
|
11
|
+
# lock = SdbLock.new('my_app_lock')
|
12
|
+
#
|
13
|
+
# Try lock, unlock.
|
14
|
+
# lock_gained = lock.try_lock("abc")
|
15
|
+
# lock.unlock("abc") if lock_gained
|
16
|
+
#
|
17
|
+
# Try lock with block. It unlocks after block execution is finished.
|
18
|
+
# executed = lock.try_lock("abc") do
|
19
|
+
# # some work
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Unlock old ones.
|
23
|
+
# lock.unlock_old(60) # Unlock all of older than 60 secs
|
24
|
+
class SdbLock
|
25
|
+
|
26
|
+
|
27
|
+
# Attribute name to be used to save locked time
|
28
|
+
LOCK_TIME = 'lock_time'
|
29
|
+
|
30
|
+
# Constructor
|
31
|
+
#
|
32
|
+
# @param [String] domain_name SimpleDB domain name
|
33
|
+
# @param [Hash] options
|
34
|
+
def initialize(domain_name, options = {})
|
35
|
+
@sdb = AWS::SimpleDB.new(options)
|
36
|
+
options = options.dup
|
37
|
+
if options.has_key?(:create_domain)
|
38
|
+
@sdb.domains.create(domain_name) if options[:create_domain]
|
39
|
+
options.delete(:create_domain)
|
40
|
+
end
|
41
|
+
@domain = @sdb.domains[domain_name]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Try to lock resource_name
|
45
|
+
#
|
46
|
+
# @param [String] resource_name name to lock
|
47
|
+
# @return [TrueClass] true when locked, unless false
|
48
|
+
def try_lock(resource_name)
|
49
|
+
attributes = {LOCK_TIME => format_time(Time.now), unless: LOCK_TIME}
|
50
|
+
item(resource_name).attributes.set(attributes)
|
51
|
+
if block_given?
|
52
|
+
begin
|
53
|
+
yield
|
54
|
+
ensure
|
55
|
+
unlock(resource_name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
true
|
59
|
+
rescue AWS::SimpleDB::Errors::ConditionalCheckFailed
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
# lock resource_name
|
64
|
+
# It blocks until lock is succeeded.
|
65
|
+
#
|
66
|
+
# @param [String] resource_name
|
67
|
+
def lock(resource_name)
|
68
|
+
wait_secs = 0.5
|
69
|
+
while true
|
70
|
+
lock = try_lock(resource_name)
|
71
|
+
break if lock
|
72
|
+
sleep(wait_secs)
|
73
|
+
wait_secs *= 2
|
74
|
+
end
|
75
|
+
|
76
|
+
if block_given?
|
77
|
+
begin
|
78
|
+
yield
|
79
|
+
ensure
|
80
|
+
unlock(resource_name)
|
81
|
+
end
|
82
|
+
else
|
83
|
+
true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Unlock resource_name
|
88
|
+
# @param [String] resource_name name to unlock
|
89
|
+
def unlock(resource_name, expected_lock_time = nil)
|
90
|
+
if expected_lock_time
|
91
|
+
item(resource_name).attributes.delete(LOCK_TIME, if: {LOCK_TIME => expected_lock_time})
|
92
|
+
else
|
93
|
+
item(resource_name).attributes.delete(LOCK_TIME)
|
94
|
+
end
|
95
|
+
true
|
96
|
+
rescue AWS::SimpleDB::Errors::ConditionalCheckFailed
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
# Locked time for resource_name
|
101
|
+
# @return [Time] locked time, nil if it is not locked
|
102
|
+
def locked_time(resource_name)
|
103
|
+
attribute = item(resource_name).attributes[LOCK_TIME]
|
104
|
+
lock_time_string = attribute.values.first
|
105
|
+
Time.at(lock_time_string.to_i) if lock_time_string
|
106
|
+
end
|
107
|
+
|
108
|
+
# All locked resources
|
109
|
+
#
|
110
|
+
# @param [Fixnum] age_in_seconds select resources older than this seconds
|
111
|
+
def locked_resources(age_in_seconds = nil)
|
112
|
+
if age_in_seconds
|
113
|
+
cond = older_than(age_in_seconds)
|
114
|
+
else
|
115
|
+
cond = "`#{LOCK_TIME}` is not null"
|
116
|
+
end
|
117
|
+
@domain.items.where(cond).map(&:name)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Unlock old resources.
|
121
|
+
# It is needed if any program failed to unlock by an unexpected exception
|
122
|
+
# or network failure etc.
|
123
|
+
#
|
124
|
+
# @param [Fixnum] age_in_seconds select resources older than this seconds
|
125
|
+
# @return [Array<String>] unlocked resource names
|
126
|
+
def unlock_old(age_in_seconds)
|
127
|
+
targets = locked_resources(age_in_seconds)
|
128
|
+
unlocked = []
|
129
|
+
targets.each do |resource_name|
|
130
|
+
values = item(resource_name).attributes[LOCK_TIME].values
|
131
|
+
next if !values || !values.first || values.first > format_time(Time.now - age_in_seconds)
|
132
|
+
succ = unlock(resource_name, values.first)
|
133
|
+
unlocked << resource_name if succ
|
134
|
+
end
|
135
|
+
unlocked
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def item(resource_name)
|
141
|
+
@domain.items[resource_name]
|
142
|
+
end
|
143
|
+
|
144
|
+
# Format time to compare lexicographically
|
145
|
+
def format_time(time)
|
146
|
+
# 12 digits is enough until year 9999
|
147
|
+
"%012d" % time.to_i
|
148
|
+
end
|
149
|
+
|
150
|
+
def older_than(age_in_seconds)
|
151
|
+
condition_time = Time.now.utc - age_in_seconds
|
152
|
+
"`#{LOCK_TIME}` < '#{format_time(condition_time)}'"
|
153
|
+
end
|
154
|
+
end
|
data/sdb_lock.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/sdb_lock/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["KAWACHI Takashi"]
|
6
|
+
gem.email = ["kawachi@p-lucky.net"]
|
7
|
+
gem.description = %q{Lock library using Amazon SimpleDB}
|
8
|
+
gem.summary = %q{Lock library using Amazon SimpleDB}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "sdb_lock"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = SdbLock::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'aws-sdk', '~> 1.5.7'
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rake', '~> 0.9.2.2'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sdb_lock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- KAWACHI Takashi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aws-sdk
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.5.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.5.7
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.9.2.2
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.2.2
|
46
|
+
description: Lock library using Amazon SimpleDB
|
47
|
+
email:
|
48
|
+
- kawachi@p-lucky.net
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/sdb_lock.rb
|
59
|
+
- lib/sdb_lock/version.rb
|
60
|
+
- sdb_lock.gemspec
|
61
|
+
homepage: ''
|
62
|
+
licenses: []
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
hash: -3369914292283911745
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
hash: -3369914292283911745
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.8.24
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Lock library using Amazon SimpleDB
|
91
|
+
test_files: []
|