barrage 0.0.4 → 0.1.0

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