barrage 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5e6522f3f124106fc417a39339d72c0f4638c63
4
- data.tar.gz: fca0bf19d7db713e35d6ed7f3d068176d2d48ab9
3
+ metadata.gz: 247efe0edfb9aae9c762b6698e5c90816c95f781
4
+ data.tar.gz: 117e43352f37892a675334b48e2b398a90fe167b
5
5
  SHA512:
6
- metadata.gz: 078eeb02f1f9cefc13b4fa66f37df1363702b76b1c51896638da71a2fd7129720a6b1722837a26e28b2eaef5be03757a639b91b734acbf28f0df2614d414703c
7
- data.tar.gz: 54e4ced79f10c2a3cba742006e055d2005caa46e6fe6b85e683e216ea6d71f436cb7424260166546fc9dbb4ed8bb00713a2fbc77a3c2abda0a1b3642a0b11968
6
+ metadata.gz: ef97bd700202ccdd92acb5a1c498d8ca1245fd6ac8a077acd8ce3f2f9e536de3f04989e77717128a956c30f5714f6c2947e3a3f115a19ec937a428d448ba16af
7
+ data.tar.gz: 27265f705dfb1d087a441c86747f70496a505346751a7bbf24c14ff9e9357ec8a91a680a901ff446f3fff52716332f09760c9407c423906bfbf0202eb29aecf3
@@ -1,3 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.1
3
+ - 2.0.0
4
+ - 2.1.2
5
+ services:
6
+ - redis-server
7
+ script: bundle exec rake spec
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.1.0
4
+
5
+ * Fix RedisWorkerId's worker_id handling
6
+ * Release old worker_id only if it is before ttl
7
+ * Add required_options to generators
8
+
3
9
  ## 0.0.4
4
10
 
5
11
  * [redis_worker_id generator] Release current worker_id before reaching real ttl
@@ -0,0 +1,11 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec,
5
+ cmd: "bundle exec rspec",
6
+ all_after_pass: true,
7
+ all_on_start: true do
8
+ watch(%r{^spec/.+_spec\.rb$})
9
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
10
+ watch('spec/spec_helper.rb') { "spec" }
11
+ end
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Barrage
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/barrage.svg)](http://badge.fury.io/rb/barrage)
4
+ [![Coverage Status](https://img.shields.io/coveralls/drecom/barrage.svg)](https://coveralls.io/r/drecom/barrage)
5
+
3
6
  Distributed ID generator(like twitter/snowflake)
4
7
 
5
8
  ## Installation
@@ -34,7 +37,7 @@ barrage = Barrage.new(
34
37
  ]
35
38
  )
36
39
  barrage.next
37
- # => Generated ID
40
+ # => Generated 64bit ID
38
41
  ```
39
42
 
40
43
  ### Generators
@@ -48,13 +51,14 @@ barrage.next
48
51
  ```ruby
49
52
  module Barrage::Generators
50
53
  class YourOwnGenerator < Base
54
+ self.required_options += %w(your_option_value)
51
55
  def generate
52
- # generated code
53
- end
56
+ # generated code
57
+ end
54
58
 
55
59
  def your_option_value
56
- options["your_option_value"]
57
- end
60
+ options["your_option_value"]
61
+ end
58
62
  end
59
63
  end
60
64
 
@@ -22,8 +22,12 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.6"
24
24
  spec.add_development_dependency "rake"
25
- spec.add_development_dependency "rspec", "3.0.0.beta2"
25
+ spec.add_development_dependency "rspec", "~> 3.0.0"
26
+ spec.add_development_dependency "rspec-its"
26
27
  spec.add_development_dependency "pry"
27
28
  spec.add_development_dependency "guard-rspec"
28
29
  spec.add_development_dependency "redis"
30
+ spec.add_development_dependency "fakeredis"
31
+ spec.add_development_dependency "delorean"
32
+ spec.add_development_dependency "coveralls"
29
33
  end
@@ -50,3 +50,5 @@ class Barrage
50
50
  @generators.inject(0) {|sum, g| sum += g.length }
51
51
  end
52
52
  end
53
+
54
+ require "barrage/generators"
@@ -1,9 +1,18 @@
1
+ require 'barrage/generators'
2
+ require 'active_support/core_ext/class/attribute'
3
+
1
4
  class Barrage
2
5
  module Generators
3
6
  class Base
4
7
  attr_reader :options
8
+ class_attribute :required_options, :available_options
9
+ self.required_options = %w(length)
10
+ self.available_options = []
5
11
 
6
12
  def initialize(options = {})
13
+ if (missing = missing_required_options(options)) && !missing.empty?
14
+ raise ArgumentError, "Missing Required options: #{missing.join(', ')}"
15
+ end
7
16
  @options = options
8
17
  end
9
18
 
@@ -18,6 +27,12 @@ class Barrage
18
27
  def current
19
28
  raise NotImplemented, "Please Override"
20
29
  end
30
+
31
+ private
32
+
33
+ def missing_required_options(given_options)
34
+ required_options.reject { |k| given_options.has_key?(k) }
35
+ end
21
36
  end
22
37
  end
23
38
  end
@@ -3,6 +3,8 @@ require 'barrage/generators/base'
3
3
  class Barrage
4
4
  module Generators
5
5
  class Msec < Base
6
+ self.required_options += %w(start_at)
7
+
6
8
  def generate
7
9
  ((Time.now.to_f * 1000).round - start_at) & (2 ** length - 1)
8
10
  end
@@ -5,6 +5,7 @@ class Barrage
5
5
  module Generators
6
6
  class RedisWorkerId < Base
7
7
  RACE_CONDITION_TTL = 30
8
+ self.required_options += %w(ttl)
8
9
 
9
10
  def initialize(options = {})
10
11
  @worker_id = nil
@@ -20,22 +21,23 @@ class Barrage
20
21
  now = Time.now.to_i
21
22
  if @worker_ttl - now <= 0
22
23
  @data[1] = @worker_id = renew_worker_id
23
- # check redis when passed half of real ttl
24
+ # check redis after half of real ttl
24
25
  @worker_ttl = now + ttl / 2
25
26
  @real_ttl = now + ttl
27
+ @data[2] = @real_ttl
26
28
  end
27
29
  @worker_id
28
30
  end
29
31
  alias_method :current, :generate
30
32
 
31
- def redis
32
- @redis ||= @data[0] = Redis.new(options["redis"] || {})
33
- end
34
-
35
33
  def ttl
36
34
  options["ttl"]
37
35
  end
38
36
 
37
+ def redis
38
+ @redis ||= @data[0] = Redis.new(options["redis"] || {})
39
+ end
40
+
39
41
  class Finalizer
40
42
  def initialize(data)
41
43
  @pid = $$
@@ -44,10 +46,10 @@ class Barrage
44
46
 
45
47
  def call(*args)
46
48
  return if @pid != $$
47
- redis, worker_id = *@data
49
+ redis, worker_id, real_ttl = *@data
48
50
 
49
51
  if redis.is_a?(Redis) and redis.connected?
50
- redis.del("barrage:worker:#{worker_id}")
52
+ redis.del("barrage:worker:#{worker_id}") if real_ttl > Time.now.to_i
51
53
  redis.client.disconnect
52
54
  end
53
55
  end
@@ -62,30 +64,41 @@ class Barrage
62
64
  new_worker_id = redis.evalsha(
63
65
  script_sha,
64
66
  argv: [2 ** length, rand(2 ** length), @worker_id, ttl, RACE_CONDITION_TTL]
65
- ).to_i
66
- new_worker_id
67
+ )
68
+ new_worker_id or raise StandardError, "Renew redis worker id failed"
69
+ return new_worker_id.to_i
67
70
  end
68
71
 
69
72
  def script_sha
70
73
  @script_sha ||=
71
74
  redis.script(:load, <<-EOF.gsub(/^ {12}/, ''))
72
- local max_value = ARGV[1]
75
+ local max_value = tonumber(ARGV[1])
73
76
  local new_worker_id = ARGV[2]
74
77
  local old_worker_id = ARGV[3]
75
- local ttl = ARGV[4]
76
- local race_condition_ttl = ARGV[5]
78
+ local ttl = tonumber(ARGV[4])
79
+ local race_condition_ttl = tonumber(ARGV[5])
80
+ local loop_cnt = 0
81
+
82
+ local worker_id = nil
83
+ local candidate_worker_id = tonumber(new_worker_id)
77
84
 
78
85
  if type(old_worker_id) == "string" and string.len(old_worker_id) > 0 and redis.call('EXISTS', "barrage:worker:" .. old_worker_id) == 1 then
79
86
  redis.call("EXPIRE", "barrage:worker:" .. old_worker_id, ttl + race_condition_ttl)
80
- new_worker_id = old_worker_id
87
+ worker_id = old_worker_id
81
88
  else
82
- while redis.call("SETNX", "barrage:worker:" .. new_worker_id, 1) == 0
89
+ while redis.call("SETNX", "barrage:worker:" .. candidate_worker_id, 1) == 0 and loop_cnt < max_value
83
90
  do
84
- new_worker_id = (new_worker_id + 1) % max_value
91
+ candidate_worker_id = (candidate_worker_id + 1) % max_value
92
+ loop_cnt = loop_cnt + 1
93
+ end
94
+ if loop_cnt >= max_value then
95
+ return nil
96
+ else
97
+ worker_id = candidate_worker_id
85
98
  end
86
- redis.call("EXPIRE", "barrage:worker:" .. new_worker_id, ttl + race_condition_ttl)
99
+ redis.call("EXPIRE", "barrage:worker:" .. worker_id, ttl + race_condition_ttl)
87
100
  end
88
- return new_worker_id
101
+ return worker_id
89
102
  EOF
90
103
  end
91
104
  end
@@ -1,3 +1,3 @@
1
1
  class Barrage
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'barrage/generators/msec'
3
+
4
+ describe Barrage::Generators::Msec do
5
+ context "When initialized" do
6
+ subject { described_class.new(options) }
7
+
8
+ context "with empty hash" do
9
+ let(:options) { {} }
10
+
11
+ it { expect { subject }.to raise_error(ArgumentError) }
12
+ end
13
+
14
+ context "with required options" do
15
+ let(:options) { {"length" => 16, "start_at" => (Time.now.to_f * 1000).round } }
16
+
17
+ it { is_expected.to be_instance_of(Barrage::Generators::Msec) }
18
+ its(:length) { is_expected.to eq(options["length"]) }
19
+ its(:start_at) { is_expected.to eq(options["start_at"]) }
20
+ end
21
+ end
22
+
23
+ describe "#generate" do
24
+ subject { described_class.new(options).generate }
25
+ let(:length) { 39 }
26
+ let(:start_at) { (Time.parse("2014-01-01 00:00:00").to_f*1000).round }
27
+ let(:options) { {"length" => length, "start_at" => start_at} }
28
+
29
+ it { is_expected.to be_instance_of(Fixnum) }
30
+ it { is_expected.to be < 2 ** length }
31
+
32
+ context "generate two numbers between 10 millisecond " do
33
+ subject { described_class.new(options) }
34
+
35
+ it "numbers difference that generated in interval 10 millisecond should exactly 10" do
36
+ t = Time.parse("2014-01-01 00:00:00")
37
+ first = time_travel_to(t) { subject.generate }
38
+ second = time_travel_to(Time.at(t.to_i, 10000)) { subject.generate }
39
+ expect(second - first).to eq(10)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'barrage/generators/redis_worker_id'
3
+
4
+ describe Barrage::Generators::RedisWorkerId do
5
+ context "When initialized" do
6
+ subject { described_class.new(options) }
7
+
8
+ context "with empty hash" do
9
+ let(:options) { {} }
10
+
11
+ it { expect { subject }.to raise_error(ArgumentError) }
12
+ end
13
+
14
+ context "with required options" do
15
+ let(:options) { {"length" => 16, "ttl" => 300} }
16
+
17
+ it { is_expected.to be_instance_of(Barrage::Generators::RedisWorkerId) }
18
+ its(:length) { is_expected.to eq(options["length"]) }
19
+ its(:ttl) { is_expected.to eq(options["ttl"]) }
20
+ end
21
+ end
22
+
23
+ describe "#generate" do
24
+ subject { described_class.new(options).generate }
25
+ let(:length) { 8 }
26
+ let(:ttl) { 300 }
27
+ let(:options) { {"length" => length, "ttl" => ttl} }
28
+
29
+ it { is_expected.to be_instance_of(Fixnum) }
30
+ it { is_expected.to be < 2 ** length }
31
+
32
+ context "on many instances" do
33
+ subject { 64.times.map { described_class.new(options) } }
34
+ let(:length) { 8 }
35
+
36
+ it "should generate unique numbers" do
37
+ numbers = subject.map(&:generate)
38
+ expect(numbers.size).to eq(numbers.uniq.size)
39
+ end
40
+
41
+ it "generates numbers should less than length" do
42
+ expect(subject.all? { |s| s.generate < (2 ** length) }).to be true
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'barrage/generators/sequence'
3
+
4
+ describe Barrage::Generators::Sequence do
5
+ context "When initialized" do
6
+ subject { described_class.new(options) }
7
+
8
+ context "with empty hash" do
9
+ let(:options) { {} }
10
+
11
+ it { expect { subject }.to raise_error(ArgumentError) }
12
+ end
13
+
14
+ context "with required options" do
15
+ let(:options) { {"length" => 16 } }
16
+
17
+ it { is_expected.to be_instance_of(Barrage::Generators::Sequence) }
18
+ its(:length) { is_expected.to eq(options["length"]) }
19
+ end
20
+ end
21
+
22
+ describe "#generate" do
23
+ subject { described_class.new(options).generate }
24
+ let(:length) { 9 }
25
+ let(:options) { {"length" => length } }
26
+
27
+ it { is_expected.to be_instance_of(Fixnum) }
28
+ it { is_expected.to be < 2 ** length }
29
+
30
+ context "When sequence is 0" do
31
+ before do
32
+ subject.instance_variable_set(:@sequence, 0)
33
+ end
34
+ subject { described_class.new(options) }
35
+
36
+ it "should generates '1'" do
37
+ expect(subject.generate).to eq(1)
38
+ end
39
+
40
+ it "if generate twice in a row, generated numbers should be in succession" do
41
+ first = subject.generate
42
+ second = subject.generate
43
+ expect(first.succ).to eq(second)
44
+ end
45
+ end
46
+
47
+ context "When sequence is max" do
48
+ before do
49
+ subject.instance_variable_set(:@sequence, (2 ** length) - 1)
50
+ end
51
+ subject { described_class.new(options) }
52
+
53
+ it "should generates zero" do
54
+ expect(subject.generate).to be_zero
55
+ end
56
+ end
57
+ end
58
+ end
@@ -4,4 +4,45 @@ describe Barrage do
4
4
  it 'has a version number' do
5
5
  expect(Barrage::VERSION).not_to be nil
6
6
  end
7
+
8
+ context "when initialized" do
9
+ subject { Barrage.new(options) }
10
+
11
+ context "with empty generators" do
12
+ let(:options) { {"generators" => []} }
13
+ its(:generators) { is_expected.to be_empty }
14
+ end
15
+
16
+ context "with generators" do
17
+ let(:options) {
18
+ {
19
+ "generators" => [
20
+ {"name" => "msec", "length" => 39, "start_at" => 1396278000000},
21
+ {"name" => "redis_worker_id", "length" => 16, "ttl" => 300},
22
+ {"name" => "sequence", "length" => 9}
23
+ ]
24
+ }
25
+ }
26
+ it { expect(subject.generators.size).to eq(options["generators"].size) }
27
+ its(:generators) {
28
+ is_expected.to contain_exactly(
29
+ be_kind_of(Barrage::Generators::Msec),
30
+ be_kind_of(Barrage::Generators::RedisWorkerId),
31
+ be_kind_of(Barrage::Generators::Sequence)
32
+ )
33
+ }
34
+
35
+ describe "#generate" do
36
+ let(:barrage) { described_class.new(options) }
37
+ subject { barrage.generate }
38
+ it { is_expected.to be_kind_of(Integer) }
39
+ it { is_expected.to be < 2 ** barrage.length }
40
+ end
41
+
42
+ describe "#length" do
43
+ subject { described_class.new(options).length }
44
+ it { is_expected.to eq(options["generators"].map { |h| h["length"]}.inject(:+)) }
45
+ end
46
+ end
47
+ end
7
48
  end
@@ -1,7 +1,15 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'bundler/setup'
3
+ require 'rspec/its'
4
+ require 'delorean'
5
+
6
+ require 'coveralls'
7
+ Coveralls.wear!
8
+
2
9
  require 'barrage'
3
10
 
4
11
  RSpec.configure do |config|
12
+ include Delorean
5
13
  config.filter_run focus: true
6
- config.run_all_when_everything_filtered
14
+ config.run_all_when_everything_filtered = true
7
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: barrage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - gussan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-24 00:00:00.000000000 Z
11
+ date: 2014-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -56,16 +56,30 @@ dependencies:
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '='
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 3.0.0.beta2
61
+ version: 3.0.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '='
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 3.0.0.beta2
68
+ version: 3.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-its
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: pry
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,48 @@ dependencies:
108
122
  - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: fakeredis
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: delorean
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: coveralls
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
111
167
  description: Distributed id generator
112
168
  email:
113
169
  - egussan@gmail.com
@@ -120,6 +176,7 @@ files:
120
176
  - ".travis.yml"
121
177
  - CHANGELOG.md
122
178
  - Gemfile
179
+ - Guardfile
123
180
  - LICENSE.txt
124
181
  - README.md
125
182
  - Rakefile
@@ -133,6 +190,9 @@ files:
133
190
  - lib/barrage/generators/sequence.rb
134
191
  - lib/barrage/generators/timestamp.rb
135
192
  - lib/barrage/version.rb
193
+ - spec/barrage/generators/msec_spec.rb
194
+ - spec/barrage/generators/redis_worker_id_spec.rb
195
+ - spec/barrage/generators/sequence_spec.rb
136
196
  - spec/barrage_spec.rb
137
197
  - spec/spec_helper.rb
138
198
  homepage: http://github.com/drecom/barrage
@@ -160,5 +220,8 @@ signing_key:
160
220
  specification_version: 4
161
221
  summary: Distributed id generator
162
222
  test_files:
223
+ - spec/barrage/generators/msec_spec.rb
224
+ - spec/barrage/generators/redis_worker_id_spec.rb
225
+ - spec/barrage/generators/sequence_spec.rb
163
226
  - spec/barrage_spec.rb
164
227
  - spec/spec_helper.rb