hivent 1.0.4 → 1.0.5

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: e360be776496b49ad0334d22d5bc52d20554829a
4
- data.tar.gz: 8a3027f19ecf4bebad1701a0d575f8e56b9a8c4a
3
+ metadata.gz: bbc8f5896f4204e96e80a1de33bffd52ffe26e0f
4
+ data.tar.gz: 47590d0d52a5d0ef933bac1aa4f3346ca4b5bfbc
5
5
  SHA512:
6
- metadata.gz: 76d2ad583fae68f5470124f671c69221d5e9db4ffe76576c73bdf16f75357cf05f3cd6c49fd412450d18f2b2fe1294553caa0bd9ada38b6a59feb433f7e1ba35
7
- data.tar.gz: f9ed4579ff32e15235f6c066e10390721e235d88f64971e5f67e1725b30d9b4a58dd3eb364de34c34dff70d719f36a007ade0c64faece3d0263edd9674d6d28b
6
+ metadata.gz: 42af01f8e3b8152d0b2a90dc963907d28abc10c74d6d5f557519dc66267968bc54c14cb22b39e9f85ea8beaae7c57b4f9d64263e52995448929d88604285273b
7
+ data.tar.gz: c09144a1cf4542f74ed8c5bfa5d372a9039bcadc8973f0ff89b1d902b92a264765daabe24e3a04831f0d9b4e82b12c4f9997c234364951b585d91f518d1f5e79
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -22,13 +22,16 @@ Gem::Specification.new do |spec|
22
22
  spec.add_dependency "activesupport", "~> 5.0"
23
23
  spec.add_dependency "retryable", "~> 2.0"
24
24
  spec.add_dependency "redis", "~> 3.3"
25
- spec.add_dependency "event_emitter", "~> 0.2"
25
+ spec.add_dependency "emittr", "~> 0.1"
26
26
 
27
27
  spec.add_development_dependency "bundler", "~> 1.12"
28
28
  spec.add_development_dependency "rspec", "~> 3.5"
29
29
  spec.add_development_dependency "rspec-its", "~> 1.2"
30
+ spec.add_development_dependency "rspec-eventually", "~> 0.2"
30
31
  spec.add_development_dependency "pry-byebug", "~> 3.4"
31
32
  spec.add_development_dependency "simplecov", "~> 0.12"
32
33
  spec.add_development_dependency "codeclimate-test-reporter", "~> 0.6"
33
34
  spec.add_development_dependency "rubocop", "~> 0.43"
35
+ spec.add_development_dependency "gem-release", "~> 0.7"
36
+ spec.add_development_dependency "rake", "~> 11.3"
34
37
  end
@@ -3,7 +3,7 @@ require "active_support"
3
3
  require "active_support/core_ext"
4
4
  require "retryable"
5
5
  require "json"
6
- require "event_emitter"
6
+ require "emittr"
7
7
 
8
8
  require "hivent/config"
9
9
 
@@ -3,7 +3,7 @@ module Hivent
3
3
 
4
4
  class Emitter
5
5
 
6
- include EventEmitter
6
+ include Emittr::Events
7
7
  attr_accessor :events
8
8
 
9
9
  WILDCARD = :all
@@ -18,6 +18,10 @@ module Hivent
18
18
  end
19
19
  end
20
20
 
21
+ def emit(name, *data)
22
+ super(name.to_sym, *data)
23
+ end
24
+
21
25
  private
22
26
 
23
27
  def emittable_event_names(payload)
@@ -8,6 +8,7 @@ module Hivent
8
8
  include Hivent::Redis::Extensions
9
9
 
10
10
  LUA_CONSUMER = File.expand_path("../lua/consumer.lua", __FILE__)
11
+ LUA_HEARTBEAT = File.expand_path("../lua/heartbeat.lua", __FILE__)
11
12
  # In milliseconds
12
13
  SLEEP_TIME = 200
13
14
  CONSUMER_TTL = 1000
@@ -21,22 +22,23 @@ module Hivent
21
22
  end
22
23
 
23
24
  def run!
25
+ start_heartbeat!
24
26
  consume while !@stop
25
27
  end
26
28
 
27
29
  def stop!
28
30
  @stop = true
31
+ stop_heartbeat!
29
32
  end
30
33
 
31
34
  def queues
32
- script(LUA_CONSUMER, @service_name, @name, CONSUMER_TTL)
35
+ script(LUA_CONSUMER, @service_name, @name, CONSUMER_TTL) || []
33
36
  end
34
37
 
35
38
  def consume
36
39
  to_process = items
37
40
 
38
41
  to_process.each do |(queue, item)|
39
- @redis.rpop(queue)
40
42
  payload = nil
41
43
  begin
42
44
  payload = JSON.parse(item).with_indifferent_access
@@ -48,6 +50,8 @@ module Hivent
48
50
  @redis.lpush(dead_letter_queue_name(queue), item)
49
51
 
50
52
  @life_cycle_event_handler.event_processing_failed(e, payload, item, dead_letter_queue_name(queue))
53
+ ensure
54
+ @redis.rpop(queue)
51
55
  end
52
56
  end
53
57
 
@@ -56,6 +60,26 @@ module Hivent
56
60
 
57
61
  private
58
62
 
63
+ def start_heartbeat!
64
+ stop_heartbeat!
65
+
66
+ @heartbeat = Thread.new do
67
+ loop do
68
+ heartbeat!
69
+
70
+ Kernel.sleep(SLEEP_TIME.to_f / 1000)
71
+ end
72
+ end
73
+ end
74
+
75
+ def stop_heartbeat!
76
+ @heartbeat.exit if @heartbeat.present?
77
+ end
78
+
79
+ def heartbeat!
80
+ script(LUA_HEARTBEAT, @service_name, @name, CONSUMER_TTL)
81
+ end
82
+
59
83
  def items
60
84
  queues
61
85
  .map { |queue| [queue, @redis.lindex(queue, -1)] }
@@ -50,25 +50,6 @@ local function table_eq(table1, table2)
50
50
  return recurse(table1, table2)
51
51
  end
52
52
 
53
- local function keepalive(service, consumer)
54
- redis.call("SET", service .. ":" .. consumer .. ":alive", "true", "PX", CONSUMER_TTL)
55
- redis.call("SADD", service .. ":consumers", consumer)
56
- end
57
-
58
- local function cleanup(service)
59
- local consumer_index_key = service .. ":consumers"
60
- local consumers = redis.call("SMEMBERS", consumer_index_key)
61
-
62
- for _, consumer in ipairs(consumers) do
63
- local consumer_status_key = service .. ":" .. consumer .. ":alive"
64
- local alive = redis.call("GET", consumer_status_key)
65
-
66
- if not alive then
67
- redis.call("SREM", consumer_index_key, consumer)
68
- end
69
- end
70
- end
71
-
72
53
  local function distribute(consumers, partition_count)
73
54
  local distribution = {}
74
55
  local consumer_count = table.getn(consumers)
@@ -163,17 +144,4 @@ local function rebalance(service_name, consumer_name)
163
144
  end
164
145
  end
165
146
 
166
- local function heartbeat(service_name, consumer_name)
167
- -- keep consumer alive
168
- keepalive(service_name, consumer_name)
169
-
170
- -- clean up dead consumers
171
- cleanup(service_name)
172
-
173
- -- rebalance
174
- local new_config = rebalance(service_name, consumer_name)
175
-
176
- return new_config
177
- end
178
-
179
- return heartbeat(service_name, consumer_name)
147
+ return rebalance(service_name, consumer_name)
@@ -0,0 +1,34 @@
1
+ local service_name = ARGV[1]
2
+ local consumer_name = ARGV[2]
3
+ local CONSUMER_TTL = ARGV[3]
4
+
5
+ local function keepalive(service, consumer)
6
+ redis.call("SET", service .. ":" .. consumer .. ":alive", "true", "PX", CONSUMER_TTL)
7
+ redis.call("SADD", service .. ":consumers", consumer)
8
+ end
9
+
10
+ local function cleanup(service)
11
+ local consumer_index_key = service .. ":consumers"
12
+ local consumers = redis.call("SMEMBERS", consumer_index_key)
13
+
14
+ for _, consumer in ipairs(consumers) do
15
+ local consumer_status_key = service .. ":" .. consumer .. ":alive"
16
+ local alive = redis.call("GET", consumer_status_key)
17
+
18
+ if not alive then
19
+ redis.call("SREM", consumer_index_key, consumer)
20
+ end
21
+ end
22
+ end
23
+
24
+ local function heartbeat(service_name, consumer_name)
25
+ -- keep consumer alive
26
+ keepalive(service_name, consumer_name)
27
+
28
+ -- clean up dead consumers
29
+ cleanup(service_name)
30
+
31
+ return true
32
+ end
33
+
34
+ return heartbeat(service_name, consumer_name)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Hivent
3
3
 
4
- VERSION = File.read(File.expand_path('../../../.version', __FILE__)).strip.freeze
4
+ VERSION = "1.0.5".freeze
5
5
 
6
6
  end
@@ -83,7 +83,7 @@ describe Hivent::AbstractSignal do
83
83
 
84
84
  describe "#receive" do
85
85
  after :each do
86
- Hivent.emitter.remove_listener name
86
+ Hivent.emitter.off name
87
87
  Hivent.emitter.events.clear
88
88
  end
89
89
 
@@ -1,4 +1,3 @@
1
- # frozen_string_literal: true
2
1
  require "spec_helper"
3
2
 
4
3
  describe Hivent::Redis::Consumer do
@@ -7,12 +6,19 @@ describe Hivent::Redis::Consumer do
7
6
  let(:redis) { Redis.new(url: REDIS_URL) }
8
7
  let(:service_name) { "a_service" }
9
8
  let(:consumer_name) { "a_consumer" }
10
- let(:life_cycle_event_handler) { double("Hivent::LifeCycleEventHandler").as_null_object }
9
+ let(:life_cycle_event_handler) { double("Hivent::Redis::LifeCycleEventHandler").as_null_object }
10
+
11
+ before :each do
12
+ stub_const("#{described_class}::CONSUMER_TTL", 1000)
13
+ end
11
14
 
12
15
  after :each do
16
+ Hivent.emitter.off
13
17
  redis.flushall
14
18
 
15
- Hivent.emitter.__events.clear
19
+ Thread.list.each do |thread|
20
+ thread.exit unless thread == Thread.current
21
+ end
16
22
  end
17
23
 
18
24
  describe "#queues" do
@@ -20,8 +26,8 @@ describe Hivent::Redis::Consumer do
20
26
  # 1. Marks every consumer as "alive"
21
27
  # 2. Resets every consumer
22
28
  # 3. Distributes partitions evenly
23
- 3.times do
24
- consumers.each(&:queues)
29
+ consumers.map do |consumer|
30
+ Thread.new { consumer.run! }
25
31
  end
26
32
  end
27
33
 
@@ -34,8 +40,13 @@ describe Hivent::Redis::Consumer do
34
40
 
35
41
  let(:partition_count) { 2 }
36
42
 
43
+ before :each do
44
+ Thread.new { consumer.run! }
45
+ sleep 0.1
46
+ end
47
+
37
48
  it "returns all available partitions" do
38
- expect(subject.length).to eq(partition_count)
49
+ expect { subject.length }.to eventually eq(partition_count)
39
50
  end
40
51
  end
41
52
 
@@ -48,89 +59,78 @@ describe Hivent::Redis::Consumer do
48
59
  before :each do
49
60
  # Hearbeat from first consumer,
50
61
  # marking it as "alive"
51
- consumer1.queues
62
+ Thread.new { consumer1.run! }
63
+ sleep 0.1
52
64
  end
53
65
 
54
66
  it "assigns all available partitions to the living consumer" do
55
- distribution = [consumer1.queues, consumer2.queues]
56
-
57
- expect(distribution.map(&:length)).to eq([2, 0])
67
+ expect { [consumer1.queues, consumer2.queues].map(&:length) }.to eventually eq([2, 0])
58
68
  end
59
69
 
60
70
  describe "balancing" do
61
- it "resets the first consumer for rebalancing" do
62
- # Marks consumer 1 as alive, assigning all partitions
63
- consumer1.queues
64
- # Marks consumer 2 as alive, assigning 0 partitions to
65
- # start rebalancing
66
- consumer2.queues
67
-
68
- # Assigns 0 partitions to finish resetting
69
- expect(consumer1.queues.length).to eq(0)
70
- end
71
-
72
71
  it "assigns half the partitions after reset" do
72
+ threads = []
73
73
  # Fully resets
74
- consumer1.queues
75
- consumer2.queues
76
- consumer1.queues
74
+ threads << Thread.new { consumer1.run! }
75
+ sleep 0.1
76
+ threads << Thread.new { consumer2.run! }
77
+ sleep 0.1
77
78
 
78
79
  # Distributes partitions across consumers
79
- expect(consumer2.queues.length).to eq(1)
80
+ expect { consumer2.queues.length }.to eventually eq(1)
80
81
  end
81
82
 
82
83
  it "rebalances partitions across both consumers" do
83
- consumer1.queues
84
- consumer2.queues
85
- consumer1.queues
86
- consumer2.queues
84
+ threads = []
85
+
86
+ threads << Thread.new { consumer1.run! }
87
+ sleep 0.1
88
+ threads << Thread.new { consumer2.run! }
89
+ sleep 0.1
90
+
91
+ Thread.new { consumer1.stop! }
92
+ Thread.new { consumer2.stop! }
93
+ sleep 0.1
94
+
95
+ threads << Thread.new { consumer1.run! }
96
+ sleep 0.1
97
+ threads << Thread.new { consumer2.run! }
98
+ sleep 0.1
87
99
 
88
100
  # Distributes partitions across consumers
89
- expect(consumer1.queues.length).to eq(1)
101
+ expect { consumer1.queues.length }.to eventually eq(1)
90
102
  end
91
103
 
92
104
  context "when one of the consumers dies" do
93
105
  before :each do
94
106
  stub_const("#{described_class}::CONSUMER_TTL", 50)
95
107
 
96
- balance([consumer1, consumer2])
97
- count = 0
108
+ threads = balance([consumer1, consumer2])
109
+ Thread.new { consumer1.stop! }
110
+ threads.first.exit
98
111
 
99
- while count <= 2
100
- consumer2.queues
101
- count += 1
102
-
103
- sleep described_class::CONSUMER_TTL.to_f / 1000
104
- end
112
+ sleep described_class::CONSUMER_TTL.to_f / 1000
105
113
  end
106
114
 
107
115
  it "assigns those consumer's partitions to another consumer" do
108
- expect(consumer2.queues.length).to eq(2)
116
+ expect { consumer2.queues.length }.to eventually eq(2)
109
117
  end
110
118
  end
111
119
  end
112
120
  end
113
121
 
114
122
  context "when both consumers are alive" do
115
- subject do
116
- [consumer1.queues, consumer2.queues]
117
- end
118
-
119
123
  before :each do
120
124
  balance([consumer1, consumer2])
121
125
  end
122
126
 
123
127
  it "returns all available partitions" do
124
- expect(subject.map(&:length)).to eq([1, 1])
128
+ expect { [consumer1.queues, consumer2.queues].map(&:length) }.to eventually eq([1, 1])
125
129
  end
126
130
  end
127
131
  end
128
132
 
129
133
  context "with more consumers than partitions" do
130
- subject do
131
- [consumer1.queues, consumer2.queues]
132
- end
133
-
134
134
  let(:consumer1) { described_class.new(redis, service_name, "#{consumer_name}1", life_cycle_event_handler) }
135
135
  let(:consumer2) { described_class.new(redis, service_name, "#{consumer_name}2", life_cycle_event_handler) }
136
136
  let(:partition_count) { 1 }
@@ -140,7 +140,7 @@ describe Hivent::Redis::Consumer do
140
140
  end
141
141
 
142
142
  it "returns all available partitions" do
143
- expect(subject.map(&:length)).to eq([1, 0])
143
+ expect { [consumer1.queues, consumer2.queues].map(&:length) }.to eventually eq([1, 0])
144
144
  end
145
145
  end
146
146
 
@@ -158,7 +158,7 @@ describe Hivent::Redis::Consumer do
158
158
  end
159
159
 
160
160
  it "returns all available partitions" do
161
- expect(subject.map(&:length)).to eq([2, 1])
161
+ expect { [consumer1.queues, consumer2.queues].map(&:length) }.to eventually eq([2, 1])
162
162
  end
163
163
  end
164
164
 
@@ -184,12 +184,20 @@ describe Hivent::Redis::Consumer do
184
184
  let(:producer) { Hivent::Redis::Producer.new(redis) }
185
185
 
186
186
  before :each do
187
+ Thread.new { consumer.run! }
188
+ sleep 0.1
189
+
187
190
  redis.set("#{service_name}:partition_count", partition_count)
188
191
  redis.sadd(event[:meta][:name], service_name)
189
192
 
190
193
  producer.write(event[:meta][:name], event.to_json, 0)
191
194
  end
192
195
 
196
+ after :each do
197
+ Thread.new { consumer.stop! }
198
+ sleep 0.1
199
+ end
200
+
193
201
  context "when there are items ready to be consumed" do
194
202
 
195
203
  it "emits the item with indifferent access" do
@@ -300,24 +308,35 @@ describe Hivent::Redis::Consumer do
300
308
  end
301
309
 
302
310
  describe "#run!" do
303
- subject { Thread.new { consumer.run! } }
304
-
305
311
  let(:partition_count) { 2 }
306
312
 
307
313
  before :each do
308
314
  redis.set("#{service_name}:partition_count", partition_count)
309
315
 
310
316
  allow(consumer).to receive(:consume)
317
+
318
+ stub_const("#{described_class}::CONSUMER_TTL", 10000)
311
319
  end
312
320
 
313
- it "processes items" do
314
- thread = subject
321
+ it "starts its heartbeat" do
322
+ thread = Thread.new { consumer.run! }
315
323
 
316
- sleep 0.1
324
+ sleep 1
325
+
326
+ is_alive = redis.get("#{service_name}:#{consumer_name}:alive")
317
327
 
318
328
  thread.kill
319
329
 
330
+ expect(is_alive).to be
331
+ end
332
+
333
+ it "processes items" do
334
+ thread = Thread.new { consumer.run! }
335
+ sleep 0.2
336
+
320
337
  expect(consumer).to have_received(:consume).at_least(:once)
338
+
339
+ thread.kill
321
340
  end
322
341
 
323
342
  end
@@ -328,6 +347,22 @@ describe Hivent::Redis::Consumer do
328
347
 
329
348
  before :each do
330
349
  redis.set("#{service_name}:partition_count", partition_count)
350
+ stub_const("#{described_class}::CONSUMER_TTL", 10)
351
+ end
352
+
353
+ it "stops its heartbeat" do
354
+ thread = Thread.new do
355
+ consumer.run!
356
+ end
357
+
358
+ sleep 0.1
359
+
360
+ consumer.stop!
361
+ thread.kill
362
+
363
+ sleep 0.2
364
+
365
+ expect { redis.get("#{service_name}:#{consumer_name}:alive") }.to eventually be_nil
331
366
  end
332
367
 
333
368
  it "stops processing" do
@@ -338,6 +373,7 @@ describe Hivent::Redis::Consumer do
338
373
  sleep 0.1
339
374
 
340
375
  consumer.stop!
376
+ thread.kill
341
377
 
342
378
  # nil is returned if timeout expires
343
379
  expect(thread.join(2)).to eq(thread)
@@ -83,7 +83,7 @@ describe Hivent do
83
83
  let(:increase) { 5 }
84
84
 
85
85
  after :each do
86
- Hivent.emitter.remove_listener "my_signal:1"
86
+ Hivent.emitter.off "my_signal:1"
87
87
  end
88
88
 
89
89
  it "consumes events" do
@@ -2,6 +2,7 @@
2
2
  require 'simplecov'
3
3
 
4
4
  require 'rspec/its'
5
+ require 'rspec/eventually'
5
6
  require 'pry'
6
7
 
7
8
  require 'hivent'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hivent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Abrantes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-10 00:00:00.000000000 Z
11
+ date: 2016-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -53,19 +53,19 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.3'
55
55
  - !ruby/object:Gem::Dependency
56
- name: event_emitter
56
+ name: emittr
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.2'
61
+ version: '0.1'
62
62
  type: :runtime
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: '0.2'
68
+ version: '0.1'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec-eventually
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.2'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.2'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: pry-byebug
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +178,34 @@ dependencies:
164
178
  - - "~>"
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0.43'
181
+ - !ruby/object:Gem::Dependency
182
+ name: gem-release
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '0.7'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '0.7'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rake
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '11.3'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '11.3'
167
209
  description: ''
168
210
  email:
169
211
  - bruno@brunoabrantes.com
@@ -179,10 +221,10 @@ files:
179
221
  - ".ruby-version"
180
222
  - ".simplecov.template"
181
223
  - ".travis.yml"
182
- - ".version"
183
224
  - Gemfile
184
225
  - LICENSE
185
226
  - README.md
227
+ - Rakefile
186
228
  - bin/hivent
187
229
  - hivent.gemspec
188
230
  - lib/hivent.rb
@@ -197,6 +239,7 @@ files:
197
239
  - lib/hivent/redis/consumer.rb
198
240
  - lib/hivent/redis/extensions.rb
199
241
  - lib/hivent/redis/lua/consumer.lua
242
+ - lib/hivent/redis/lua/heartbeat.lua
200
243
  - lib/hivent/redis/lua/producer.lua
201
244
  - lib/hivent/redis/producer.rb
202
245
  - lib/hivent/redis/redis.rb
data/.version DELETED
@@ -1 +0,0 @@
1
- 1.0.4