gem_of_thrones 0.2.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/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +37 -0
- data/Rakefile +22 -0
- data/Readme.md +39 -0
- data/gem_of_thrones.gemspec +12 -0
- data/lib/gem_of_thrones.rb +38 -0
- data/lib/gem_of_thrones/version.rb +3 -0
- data/spec/gem_of_thrones_spec.rb +86 -0
- data/spec/spec_helper.rb +13 -0
- metadata +74 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
gem_of_thrones (0.2.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activesupport (3.2.3)
|
10
|
+
i18n (~> 0.6)
|
11
|
+
multi_json (~> 1.0)
|
12
|
+
benhutton-libmemcached_store (0.3.2)
|
13
|
+
memcached
|
14
|
+
diff-lcs (1.1.3)
|
15
|
+
i18n (0.6.0)
|
16
|
+
memcached (1.3.5)
|
17
|
+
multi_json (1.3.4)
|
18
|
+
rake (0.9.2)
|
19
|
+
rspec (2.6.0)
|
20
|
+
rspec-core (~> 2.6.0)
|
21
|
+
rspec-expectations (~> 2.6.0)
|
22
|
+
rspec-mocks (~> 2.6.0)
|
23
|
+
rspec-core (2.6.4)
|
24
|
+
rspec-expectations (2.6.0)
|
25
|
+
diff-lcs (~> 1.1.2)
|
26
|
+
rspec-mocks (2.6.0)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
activesupport (> 3)
|
33
|
+
benhutton-libmemcached_store
|
34
|
+
gem_of_thrones!
|
35
|
+
memcached
|
36
|
+
rake
|
37
|
+
rspec (~> 2)
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
task :default do
|
4
|
+
sh "rspec spec/"
|
5
|
+
end
|
6
|
+
|
7
|
+
# extracted from https://github.com/grosser/project_template
|
8
|
+
rule /^version:bump:.*/ do |t|
|
9
|
+
sh "git status | grep 'nothing to commit'" # ensure we are not dirty
|
10
|
+
index = ['major', 'minor','patch'].index(t.name.split(':').last)
|
11
|
+
file = 'lib/gem_of_thrones/version.rb'
|
12
|
+
|
13
|
+
version_file = File.read(file)
|
14
|
+
old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
|
15
|
+
version_parts[index] = version_parts[index].to_i + 1
|
16
|
+
version_parts[2] = 0 if index < 2 # remove patch for minor
|
17
|
+
version_parts[1] = 0 if index < 1 # remove minor for major
|
18
|
+
new_version = version_parts * '.'
|
19
|
+
File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
|
20
|
+
|
21
|
+
sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
|
22
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
Everybody wants to be king, but only one can win (synchronized via a distributed cache).<br/>
|
2
|
+
Update something everybody depends on without doing it multiple times or using a cron.
|
3
|
+
|
4
|
+
Cache must support the interface `write(key, value, :expires_in => xxx, :unless_exist => true)`,<br/>
|
5
|
+
which afaik only `ActiveSupport::Cache::MemCacheStore` and [ActiveSupport::Cache::LibmemcachedStore](https://github.com/benhutton/libmemcached_store) do.
|
6
|
+
|
7
|
+
|
8
|
+
Install
|
9
|
+
=======
|
10
|
+
gem install gem_of_thrones
|
11
|
+
Or
|
12
|
+
|
13
|
+
rails plugin install git://github.com/grosser/gem_of_thrones.git
|
14
|
+
|
15
|
+
|
16
|
+
Usage
|
17
|
+
=====
|
18
|
+
|
19
|
+
aspirant = GemOfThrones.new(
|
20
|
+
:cache => Rails.cache, # where to store the lock ?
|
21
|
+
:timeout => 60 # if current king does not react the next aspirant will take its place
|
22
|
+
)
|
23
|
+
|
24
|
+
Thread.new do
|
25
|
+
loop do
|
26
|
+
# if I can be king (there is no king or the old king did not do anything)
|
27
|
+
if aspirant.rise_to_power
|
28
|
+
# do something that should only be done by one
|
29
|
+
end
|
30
|
+
sleep 30 # if you choose a timeout greater then the throne timeout the king will always change
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Author
|
35
|
+
======
|
36
|
+
[Zendesk](http://zendesk.com)<br/>
|
37
|
+
michael@grosser.it<br/>
|
38
|
+
License: MIT<br/>
|
39
|
+
[](http://travis-ci.org/grosser/gem_of_thrones)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
name = "gem_of_thrones"
|
3
|
+
require "#{name}/version"
|
4
|
+
|
5
|
+
Gem::Specification.new name, GemOfThrones::VERSION do |s|
|
6
|
+
s.summary = "Everybody wants to be king, but only one can win (synchronized via a distributed cache)"
|
7
|
+
s.authors = ["Michael Grosser"]
|
8
|
+
s.email = "michael@grosser.it"
|
9
|
+
s.homepage = "http://github.com/grosser/#{name}"
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.license = 'MIT'
|
12
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "gem_of_thrones/version"
|
2
|
+
|
3
|
+
class GemOfThrones
|
4
|
+
def initialize(options)
|
5
|
+
@options = {
|
6
|
+
:timeout => 10 * 60,
|
7
|
+
:cache_key => "GemOfThrones.",
|
8
|
+
:cache => "You have to set :cache",
|
9
|
+
}.merge(options)
|
10
|
+
|
11
|
+
raise "Only integers are supported for timeout" if options[:timeout].is_a?(Float)
|
12
|
+
end
|
13
|
+
|
14
|
+
def rise_to_power
|
15
|
+
i_am_king! :try => !in_power?
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def i_am_king!(options)
|
21
|
+
@options[:cache].write(@options[:cache_key], myself,
|
22
|
+
:expires_in => @options[:timeout],
|
23
|
+
:unless_exist => options[:try]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def in_power?
|
28
|
+
current_king == myself
|
29
|
+
end
|
30
|
+
|
31
|
+
def current_king
|
32
|
+
@options[:cache].read(@options[:cache_key])
|
33
|
+
end
|
34
|
+
|
35
|
+
def myself
|
36
|
+
@myself ||= "#{Process.pid}-#{object_id}-#{Time.now.to_f}"
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GemOfThrones do
|
4
|
+
let(:cache){ ActiveSupport::Cache::LibmemcachedStore.new(:namespace => "GemOfThronesTest") }
|
5
|
+
|
6
|
+
before do
|
7
|
+
cache.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_game(options={})
|
11
|
+
GemOfThrones.new({:cache => cache, :mutex_timeout => 0.01}.merge(options))
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has a VERSION" do
|
15
|
+
GemOfThrones::VERSION.should =~ /^[\.\da-z]+$/
|
16
|
+
end
|
17
|
+
|
18
|
+
it "raises with unsupported timeouts" do
|
19
|
+
expect{
|
20
|
+
new_game(:timeout => 0.1)
|
21
|
+
}.to raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#rise_to_power" do
|
25
|
+
it "executes if there is no king" do
|
26
|
+
result = nil
|
27
|
+
if new_game.rise_to_power
|
28
|
+
result = 1
|
29
|
+
end
|
30
|
+
result.should == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
it "executes if the king is alive" do
|
34
|
+
result = nil
|
35
|
+
if new_game.rise_to_power
|
36
|
+
result = 1
|
37
|
+
end
|
38
|
+
if new_game.rise_to_power
|
39
|
+
result = 2
|
40
|
+
end
|
41
|
+
result.should == 1
|
42
|
+
end
|
43
|
+
|
44
|
+
it "executes if the king stays alive" do
|
45
|
+
result = nil
|
46
|
+
game = new_game
|
47
|
+
if game.rise_to_power
|
48
|
+
result = 1
|
49
|
+
end
|
50
|
+
if game.rise_to_power
|
51
|
+
result = 2
|
52
|
+
end
|
53
|
+
result.should == 2
|
54
|
+
end
|
55
|
+
|
56
|
+
it "executes if the king died" do
|
57
|
+
result = nil
|
58
|
+
if new_game(:timeout => 1).rise_to_power
|
59
|
+
result = 1
|
60
|
+
end
|
61
|
+
sleep 1.1
|
62
|
+
if new_game.rise_to_power
|
63
|
+
result = 2
|
64
|
+
end
|
65
|
+
result.should == 2
|
66
|
+
end
|
67
|
+
|
68
|
+
# using fractions of seconds leads to randomly failing tests
|
69
|
+
it "ruling keeps the king in power" do
|
70
|
+
result = nil
|
71
|
+
old_king = new_game(:timeout => 3)
|
72
|
+
if old_king.rise_to_power
|
73
|
+
result = 1
|
74
|
+
end
|
75
|
+
sleep 2
|
76
|
+
if old_king.rise_to_power
|
77
|
+
result = 2
|
78
|
+
end
|
79
|
+
sleep 2
|
80
|
+
if new_game.rise_to_power
|
81
|
+
result = 3
|
82
|
+
end
|
83
|
+
result.should == 2
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift 'lib'
|
2
|
+
require 'gem_of_thrones'
|
3
|
+
|
4
|
+
require 'memcached'
|
5
|
+
require 'active_support/cache'
|
6
|
+
require 'active_support/cache/libmemcached_store'
|
7
|
+
|
8
|
+
# needed by ActiveSupport::Cache::Store
|
9
|
+
class String
|
10
|
+
def duplicable?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gem_of_thrones
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 21
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 1
|
10
|
+
version: 0.2.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michael Grosser
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-05-09 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: michael@grosser.it
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- .travis.yml
|
31
|
+
- Gemfile
|
32
|
+
- Gemfile.lock
|
33
|
+
- Rakefile
|
34
|
+
- Readme.md
|
35
|
+
- gem_of_thrones.gemspec
|
36
|
+
- lib/gem_of_thrones.rb
|
37
|
+
- lib/gem_of_thrones/version.rb
|
38
|
+
- spec/gem_of_thrones_spec.rb
|
39
|
+
- spec/spec_helper.rb
|
40
|
+
homepage: http://github.com/grosser/gem_of_thrones
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
hash: 3
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.24
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Everybody wants to be king, but only one can win (synchronized via a distributed cache)
|
73
|
+
test_files: []
|
74
|
+
|