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 ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ .idea
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sdb_lock.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ class SdbLock
2
+ VERSION = "0.0.1"
3
+ end
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: []