chasqui 0.1.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Guardfile +1 -1
- data/Rakefile +3 -13
- data/lib/chasqui/broker.rb +9 -5
- data/lib/chasqui/multi_broker.rb +39 -13
- data/lib/chasqui/version.rb +1 -1
- data/lib/chasqui/workers/resque_worker.rb +15 -30
- data/lib/chasqui/workers/sidekiq_worker.rb +23 -15
- data/lib/chasqui/workers/worker.rb +34 -0
- data/lib/chasqui.rb +13 -2
- data/spec/integration/pubsub_examples.rb +119 -0
- data/spec/integration/resque_spec.rb +4 -97
- data/spec/integration/setup/resque.rb +8 -0
- data/spec/integration/setup/sidekiq.rb +10 -0
- data/spec/integration/{subscribers.rb → setup/subscribers.rb} +0 -0
- data/spec/integration/sidekiq_spec.rb +24 -0
- data/spec/lib/chasqui/broker_spec.rb +4 -0
- data/spec/lib/chasqui/multi_broker_spec.rb +23 -13
- data/spec/lib/chasqui/workers/sidekiq_worker_spec.rb +2 -1
- data/spec/lib/chasqui_spec.rb +66 -33
- data/spec/support/chasqui_spec_helpers.rb +7 -2
- data/spec/support/fake_logger.rb +1 -0
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f308ee6114cf27680f344bbb770e4eeee946f467
|
4
|
+
data.tar.gz: 749dbc74254f92306c8204f799a99d54d5e83017
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9fe9a92bba7c1be6ef419fae3f0adbb3db94010b966773b5b32d127a5e43302a6b01bb522f33b0bfd4d291fe389d2da47576a9197cd93a172fdadff875e519b
|
7
|
+
data.tar.gz: f7957a3fd5ea629fcb5ac05f5cb80a1d8231385ee7dddf1f8b8dd9a349be580a3b5a1e7908be04ae21be682b9d84af4fd3433ab92b3199b02a348059c1b47803
|
data/Guardfile
CHANGED
data/Rakefile
CHANGED
@@ -2,22 +2,12 @@ require "bundler/gem_tasks"
|
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
4
|
require 'resque/tasks'
|
5
|
-
task 'resque:setup' => :
|
5
|
+
task 'resque:setup' => :resque_integration_environment
|
6
6
|
|
7
7
|
RSpec::Core::RakeTask.new(:spec)
|
8
8
|
|
9
9
|
task :default => :spec
|
10
10
|
|
11
|
-
task :
|
12
|
-
|
13
|
-
require 'bundler/setup'
|
14
|
-
require 'chasqui'
|
15
|
-
|
16
|
-
if ENV['CHASQUI_ENV'] == 'test'
|
17
|
-
require './spec/integration/subscribers'
|
18
|
-
end
|
19
|
-
|
20
|
-
require 'resque'
|
21
|
-
Resque.redis = ENV['REDIS_URL'] if ENV['REDIS_URL']
|
22
|
-
Resque.redis.namespace = 'chasqui'
|
11
|
+
task :resque_integration_environment do
|
12
|
+
require './spec/integration/setup/resque'
|
23
13
|
end
|
data/lib/chasqui/broker.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
1
|
require 'timeout'
|
2
2
|
|
3
3
|
class Chasqui::Broker
|
4
|
-
attr_reader :config
|
4
|
+
attr_reader :config, :redis, :redis_namespace
|
5
5
|
|
6
6
|
extend Forwardable
|
7
|
-
def_delegators :@config, :
|
7
|
+
def_delegators :@config, :inbox, :logger
|
8
8
|
|
9
9
|
ShutdownSignals = %w(INT QUIT ABRT TERM).freeze
|
10
10
|
|
11
|
-
# To prevent unsuspecting clients from blocking forever, the broker uses
|
12
|
-
# it's own private redis connection.
|
13
11
|
def initialize
|
14
12
|
@shutdown_requested = nil
|
15
13
|
@config = Chasqui.config.dup
|
16
|
-
@
|
14
|
+
@redis_namespace = @config.redis.namespace
|
15
|
+
|
16
|
+
# The broker uses it's own private redis connection for two reasons:
|
17
|
+
# 1. subscribers may use a different (or no) redis namespace than chasqui
|
18
|
+
# 2. sharing the connection with unsuspecting clients could result in
|
19
|
+
# the broker blocking forever
|
20
|
+
@redis = Redis.new @config.redis.client.options
|
17
21
|
end
|
18
22
|
|
19
23
|
def start
|
data/lib/chasqui/multi_broker.rb
CHANGED
@@ -2,11 +2,11 @@ class Chasqui::MultiBroker < Chasqui::Broker
|
|
2
2
|
|
3
3
|
def forward_event
|
4
4
|
event = receive or return
|
5
|
-
|
5
|
+
subscribers = subscribers_for(event)
|
6
6
|
|
7
7
|
redis.multi do
|
8
|
-
|
9
|
-
dispatch event,
|
8
|
+
subscribers.each do |subscriber_id|
|
9
|
+
dispatch event, subscriber_id
|
10
10
|
end
|
11
11
|
redis.rpop(in_progress_queue)
|
12
12
|
end
|
@@ -15,16 +15,23 @@ class Chasqui::MultiBroker < Chasqui::Broker
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def in_progress_queue
|
18
|
-
|
18
|
+
to_key inbox, 'in_progress'
|
19
|
+
end
|
20
|
+
|
21
|
+
def inbox_queue
|
22
|
+
to_key inbox
|
19
23
|
end
|
20
24
|
|
21
25
|
private
|
22
26
|
|
23
27
|
def receive
|
24
28
|
event = retry_failed_event || dequeue
|
25
|
-
logger.debug "received event: #{event['event']}, on channel: #{event['channel']}"
|
26
29
|
|
27
|
-
|
30
|
+
if event
|
31
|
+
JSON.parse(event).tap do |e|
|
32
|
+
logger.debug "received event: #{e['event']}, on channel: #{e['channel']}"
|
33
|
+
end
|
34
|
+
end
|
28
35
|
end
|
29
36
|
|
30
37
|
def retry_failed_event
|
@@ -36,21 +43,40 @@ class Chasqui::MultiBroker < Chasqui::Broker
|
|
36
43
|
end
|
37
44
|
|
38
45
|
def dequeue
|
39
|
-
redis.brpoplpush(
|
46
|
+
redis.brpoplpush(inbox_queue, in_progress_queue, timeout: config.broker_poll_interval).tap do |event|
|
40
47
|
if event.nil?
|
41
48
|
logger.debug "reached timeout for broker poll interval: #{config.broker_poll_interval} seconds"
|
42
49
|
end
|
43
50
|
end
|
44
51
|
end
|
45
52
|
|
46
|
-
def dispatch(event,
|
47
|
-
|
48
|
-
|
49
|
-
|
53
|
+
def dispatch(event, subscriber_id)
|
54
|
+
backend, queue = subscriber_id.split('/', 2)
|
55
|
+
job = build_job queue, event
|
56
|
+
|
57
|
+
logger.debug "dispatching event queue=#{queue} backend=#{backend} job=#{job}"
|
58
|
+
|
59
|
+
case backend
|
60
|
+
when 'resque'
|
61
|
+
redis.rpush queue, job
|
62
|
+
when 'sidekiq'
|
63
|
+
redis.lpush queue, job
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_job(queue, event)
|
68
|
+
{
|
69
|
+
class: "Chasqui::#{Chasqui.subscriber_class_name(queue)}",
|
70
|
+
args: [event]
|
71
|
+
}.to_json
|
72
|
+
end
|
73
|
+
|
74
|
+
def subscribers_for(event)
|
75
|
+
redis.smembers to_key('subscribers', event['channel'])
|
50
76
|
end
|
51
77
|
|
52
|
-
def
|
53
|
-
|
78
|
+
def to_key(*args)
|
79
|
+
([redis_namespace] + args).join(':')
|
54
80
|
end
|
55
81
|
|
56
82
|
end
|
data/lib/chasqui/version.rb
CHANGED
@@ -1,40 +1,25 @@
|
|
1
|
-
|
1
|
+
module Chasqui
|
2
|
+
class ResqueWorker < Worker
|
3
|
+
class << self
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
# Factory method to create a Resque worker class for a Chasqui::Subscriber instance.
|
6
|
-
def create(subscriber)
|
7
|
-
find_or_build_worker(subscriber).tap do |worker|
|
8
|
-
worker.class_eval do
|
9
|
-
@queue = subscriber.queue
|
10
|
-
@subscriber = subscriber
|
11
|
-
|
12
|
-
def self.perform(event)
|
13
|
-
@subscriber.perform Resque.redis, event
|
14
|
-
end
|
15
|
-
end
|
5
|
+
def namespace
|
6
|
+
Resque.redis.namespace
|
16
7
|
end
|
17
|
-
end
|
18
8
|
|
19
|
-
|
9
|
+
# Factory method to create a Resque worker class for a Chasqui::Subscriber instance.
|
10
|
+
def create(subscriber)
|
11
|
+
find_or_build_worker(subscriber, Chasqui::ResqueWorker).tap do |worker|
|
12
|
+
worker.class_eval do
|
13
|
+
@queue = subscriber.queue
|
14
|
+
@subscriber = subscriber
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
Chasqui.const_get class_name
|
26
|
-
else
|
27
|
-
Class.new(Chasqui::ResqueWorker).tap do |worker|
|
28
|
-
Chasqui.const_set class_name, worker
|
16
|
+
def self.perform(event)
|
17
|
+
@subscriber.perform Resque.redis, event
|
18
|
+
end
|
19
|
+
end
|
29
20
|
end
|
30
21
|
end
|
31
|
-
end
|
32
22
|
|
33
|
-
def class_name_for(subscriber)
|
34
|
-
queue_name_constant = subscriber.queue.gsub(/[^\w]/, '_')
|
35
|
-
"Subscriber__#{queue_name_constant}".to_sym
|
36
23
|
end
|
37
|
-
|
38
24
|
end
|
39
|
-
|
40
25
|
end
|
@@ -1,25 +1,33 @@
|
|
1
|
-
|
1
|
+
module Chasqui
|
2
|
+
class SidekiqWorker < Worker
|
3
|
+
class << self
|
2
4
|
|
3
|
-
|
5
|
+
def namespace
|
6
|
+
Sidekiq.redis { |r| r.respond_to?(:namespace) ? r.namespace : nil }
|
7
|
+
end
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
def create(subscriber)
|
10
|
+
find_or_build_worker(subscriber, Chasqui::SidekiqWorker).tap do |worker|
|
11
|
+
worker.class_eval do
|
12
|
+
include Sidekiq::Worker
|
13
|
+
sidekiq_options queue: subscriber.queue
|
14
|
+
@subscriber = subscriber
|
10
15
|
|
11
|
-
|
12
|
-
|
13
|
-
|
16
|
+
def perform(event)
|
17
|
+
Sidekiq.redis do |r|
|
18
|
+
self.class.subscriber.perform r, event
|
19
|
+
end
|
20
|
+
end
|
14
21
|
|
15
|
-
|
22
|
+
private
|
16
23
|
|
17
|
-
|
18
|
-
|
24
|
+
def self.subscriber
|
25
|
+
@subscriber
|
26
|
+
end
|
27
|
+
end
|
19
28
|
end
|
20
29
|
end
|
21
|
-
end
|
22
30
|
|
31
|
+
end
|
23
32
|
end
|
24
|
-
|
25
33
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Chasqui
|
2
|
+
class Worker
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def namespace
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
8
|
+
|
9
|
+
def create(subscriber)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def subscriber=(subscriber)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def find_or_build_worker(subscriber, superclass)
|
20
|
+
class_name = Chasqui.subscriber_class_name(subscriber.queue)
|
21
|
+
|
22
|
+
if Chasqui.const_defined? class_name
|
23
|
+
Chasqui.logger.warn "redefining subscriber class Chasqui::#{class_name}"
|
24
|
+
Chasqui.send :remove_const, class_name
|
25
|
+
end
|
26
|
+
|
27
|
+
Class.new(superclass).tap do |worker|
|
28
|
+
Chasqui.const_set class_name, worker
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/chasqui.rb
CHANGED
@@ -9,6 +9,7 @@ require "chasqui/config"
|
|
9
9
|
require "chasqui/broker"
|
10
10
|
require "chasqui/multi_broker"
|
11
11
|
require "chasqui/subscriber"
|
12
|
+
require "chasqui/workers/worker"
|
12
13
|
require "chasqui/workers/resque_worker"
|
13
14
|
require "chasqui/workers/sidekiq_worker"
|
14
15
|
|
@@ -37,8 +38,8 @@ module Chasqui
|
|
37
38
|
|
38
39
|
register_subscriber(queue, channel).tap do |sub|
|
39
40
|
sub.evaluate(&block) if block_given?
|
40
|
-
|
41
|
-
redis.sadd "subscribers:#{channel}", queue
|
41
|
+
worker = create_worker(sub)
|
42
|
+
redis.sadd "subscribers:#{channel}", subscriber_id(worker, queue)
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
@@ -46,6 +47,11 @@ module Chasqui
|
|
46
47
|
subscribers[queue.to_s]
|
47
48
|
end
|
48
49
|
|
50
|
+
def subscriber_class_name(queue)
|
51
|
+
queue_name_constant = queue.split(':').last.gsub(/[^\w]/, '_')
|
52
|
+
"Subscriber__#{queue_name_constant}".to_sym
|
53
|
+
end
|
54
|
+
|
49
55
|
def create_worker(subscriber)
|
50
56
|
case config.worker_backend
|
51
57
|
when :resque
|
@@ -60,6 +66,11 @@ module Chasqui
|
|
60
66
|
|
61
67
|
private
|
62
68
|
|
69
|
+
def subscriber_id(worker, queue)
|
70
|
+
queue_name = [worker.namespace, 'queue', queue].compact.join(':')
|
71
|
+
"#{config.worker_backend}/#{queue_name}"
|
72
|
+
end
|
73
|
+
|
63
74
|
def register_subscriber(queue, channel)
|
64
75
|
subscribers[queue.to_s] ||= Subscriber.new queue, channel
|
65
76
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples 'pubsub' do |namespace, start_workers_method|
|
4
|
+
|
5
|
+
def events_to_publish
|
6
|
+
[
|
7
|
+
{ event: 'user.signup', data: ['Kelly'] },
|
8
|
+
{ event: 'account.credit', data: [1337, 'Kelly'] },
|
9
|
+
{ event: 'account.debit', data: [10, 'Kelly'] },
|
10
|
+
{ event: 'user.signup', data: ['Travis'] },
|
11
|
+
{ event: 'account.debit', data: [9000, 'Kelly'] },
|
12
|
+
{ event: 'user.cancel', data: ['Kelly'] },
|
13
|
+
{ event: 'account.credit', data: [42, 'Travis'] },
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def expected_events
|
18
|
+
{
|
19
|
+
'app1' => [
|
20
|
+
{ event: 'user.signup', data: ['Kelly'] },
|
21
|
+
{ event: 'user.signup', data: ['Travis'] },
|
22
|
+
],
|
23
|
+
'app2' => [
|
24
|
+
{ event: 'account.credit', data: [1337, 'Kelly'] },
|
25
|
+
{ event: 'account.debit', data: [10, 'Kelly'] },
|
26
|
+
{ event: 'account.debit', data: [9000, 'Kelly'] },
|
27
|
+
{ event: 'user.cancel', data: ['Kelly'] },
|
28
|
+
{ event: 'account.credit', data: [42, 'Travis'] },
|
29
|
+
],
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
before do
|
34
|
+
@subscriber_queues = %w(app1 app2)
|
35
|
+
@redis_url = 'redis://localhost:6379/13'
|
36
|
+
@redis = Redis.new url: @redis_url
|
37
|
+
@redis.keys('*').each { |k| @redis.del k }
|
38
|
+
|
39
|
+
Chasqui.configure do |c|
|
40
|
+
c.channel = 'integration'
|
41
|
+
c.redis = @redis_url
|
42
|
+
end
|
43
|
+
|
44
|
+
@pids = []
|
45
|
+
send start_workers_method
|
46
|
+
|
47
|
+
# Wait for subscribers to register before starting the broker so we don't miss events.
|
48
|
+
sleep 1
|
49
|
+
|
50
|
+
start_chasqui_broker
|
51
|
+
end
|
52
|
+
|
53
|
+
after do
|
54
|
+
terminate_child_processes
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'works' do
|
58
|
+
events_to_publish.each do |event|
|
59
|
+
Chasqui.publish event[:event], *event[:data]
|
60
|
+
end
|
61
|
+
|
62
|
+
begin
|
63
|
+
Timeout::timeout(10) do
|
64
|
+
expected_events.each do |subscriber_queue, events|
|
65
|
+
events.each do |expected|
|
66
|
+
_, payload = @redis.blpop "#{namespace}:#{subscriber_queue}:event_log"
|
67
|
+
actual = JSON.parse payload
|
68
|
+
expect(actual['event']).to eq(expected[:event])
|
69
|
+
expect(actual['data']).to eq(expected[:data])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
rescue TimeoutError
|
74
|
+
terminate_child_processes
|
75
|
+
fail "Failed to process all events in a timely manner."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def start_chasqui_broker
|
80
|
+
@pids << fork do
|
81
|
+
exec './bin/chasqui',
|
82
|
+
'--logfile', 'tmp/integration.log',
|
83
|
+
'--redis', @redis_url,
|
84
|
+
'--debug'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def start_resque_workers
|
89
|
+
@subscriber_queues.each do |queue|
|
90
|
+
@pids << fork do
|
91
|
+
ENV['CHASQUI_ENV'] = 'test'
|
92
|
+
ENV['QUEUE'] = queue
|
93
|
+
ENV['TERM_CHILD'] = '1'
|
94
|
+
ENV['INTERVAL'] = '1'
|
95
|
+
ENV['REDIS_NAMESPACE'] = "resque:#{queue}"
|
96
|
+
ENV['REDIS_URL'] = @redis_url
|
97
|
+
exec 'bundle', 'exec', 'rake', 'resque:work'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def terminate_child_processes
|
103
|
+
Timeout::timeout(10) do
|
104
|
+
@pids.each { |pid| kill 'TERM', pid rescue nil }
|
105
|
+
end
|
106
|
+
rescue TimeoutError
|
107
|
+
@pids.each { |pid| kill 'KILL', pid rescue nil }
|
108
|
+
fail "One or more child processes failed to terminate in a timely manner"
|
109
|
+
end
|
110
|
+
|
111
|
+
def kill(signal, pid)
|
112
|
+
Process.kill signal, pid
|
113
|
+
unless signal == 'KILL'
|
114
|
+
pid, status = Process.waitpid2 pid, 0
|
115
|
+
expect(status.exitstatus).to eq(0)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -1,84 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'integration/pubsub_examples'
|
2
3
|
|
3
|
-
describe "
|
4
|
-
|
5
|
-
PUBLISH_EVENTS = [
|
6
|
-
{ event: 'user.signup', data: ['Kelly'] },
|
7
|
-
{ event: 'account.credit', data: [1337, 'Kelly'] },
|
8
|
-
{ event: 'account.debit', data: [10, 'Kelly'] },
|
9
|
-
{ event: 'user.signup', data: ['Travis'] },
|
10
|
-
{ event: 'account.debit', data: [9000, 'Kelly'] },
|
11
|
-
{ event: 'user.cancel', data: ['Kelly'] },
|
12
|
-
{ event: 'account.credit', data: [42, 'Travis'] },
|
13
|
-
]
|
14
|
-
|
15
|
-
EXPECTED_EVENTS = {
|
16
|
-
'app1' => [
|
17
|
-
{ event: 'user.signup', data: ['Kelly'] },
|
18
|
-
{ event: 'user.signup', data: ['Travis'] },
|
19
|
-
],
|
20
|
-
'app2' => [
|
21
|
-
{ event: 'account.credit', data: [1337, 'Kelly'] },
|
22
|
-
{ event: 'account.debit', data: [10, 'Kelly'] },
|
23
|
-
{ event: 'account.debit', data: [9000, 'Kelly'] },
|
24
|
-
{ event: 'user.cancel', data: ['Kelly'] },
|
25
|
-
{ event: 'account.credit', data: [42, 'Travis'] },
|
26
|
-
],
|
27
|
-
}
|
28
|
-
|
29
|
-
before do
|
30
|
-
@subscriber_queues = %w(app1 app2)
|
31
|
-
@redis_url = 'redis://localhost:6379/13'
|
32
|
-
@redis = Redis.new url: @redis_url
|
33
|
-
@redis.keys('*').each { |k| @redis.del k }
|
34
|
-
|
35
|
-
Chasqui.configure do |c|
|
36
|
-
c.channel = 'integration'
|
37
|
-
c.redis = @redis_url
|
38
|
-
end
|
39
|
-
|
40
|
-
@pids = []
|
41
|
-
start_resque_workers
|
42
|
-
|
43
|
-
# Wait for subscribers to register before starting the broker so we don't miss events.
|
44
|
-
sleep 1
|
45
|
-
|
46
|
-
start_chasqui_broker
|
47
|
-
end
|
48
|
-
|
49
|
-
after do
|
50
|
-
terminate_child_processes
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'works' do
|
54
|
-
PUBLISH_EVENTS.each do |event|
|
55
|
-
Chasqui.publish event[:event], *event[:data]
|
56
|
-
end
|
57
|
-
|
58
|
-
begin
|
59
|
-
Timeout::timeout(10) do
|
60
|
-
EXPECTED_EVENTS.each do |subscriber_queue, events|
|
61
|
-
events.each do |expected|
|
62
|
-
_, payload = @redis.blpop "chasqui:#{subscriber_queue}:event_log"
|
63
|
-
actual = JSON.parse payload
|
64
|
-
expect(actual['event']).to eq(expected[:event])
|
65
|
-
expect(actual['data']).to eq(expected[:data])
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
rescue TimeoutError
|
70
|
-
fail "Failed to process all events in a timely manner."
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def start_chasqui_broker
|
75
|
-
@pids << fork do
|
76
|
-
exec './bin/chasqui',
|
77
|
-
'--logfile', 'tmp/resque-spec.log',
|
78
|
-
'--redis', @redis_url,
|
79
|
-
'--debug'
|
80
|
-
end
|
81
|
-
end
|
4
|
+
describe "resque integration", integration: true do
|
5
|
+
include_examples 'pubsub', :resque, :start_resque_workers
|
82
6
|
|
83
7
|
def start_resque_workers
|
84
8
|
@subscriber_queues.each do |queue|
|
@@ -86,28 +10,11 @@ describe "Chasqui resque integration" do
|
|
86
10
|
ENV['CHASQUI_ENV'] = 'test'
|
87
11
|
ENV['QUEUE'] = queue
|
88
12
|
ENV['TERM_CHILD'] = '1'
|
89
|
-
ENV['INTERVAL'] = '
|
13
|
+
ENV['INTERVAL'] = '1'
|
90
14
|
ENV['REDIS_NAMESPACE'] = "resque:#{queue}"
|
91
15
|
ENV['REDIS_URL'] = @redis_url
|
92
16
|
exec 'bundle', 'exec', 'rake', 'resque:work'
|
93
17
|
end
|
94
18
|
end
|
95
19
|
end
|
96
|
-
|
97
|
-
def terminate_child_processes
|
98
|
-
Timeout::timeout(10) do
|
99
|
-
@pids.each { |pid| kill 'TERM', pid rescue nil }
|
100
|
-
end
|
101
|
-
rescue TimeoutError
|
102
|
-
@pids.each { |pid| kill 'KILL', pid rescue nil }
|
103
|
-
fail "One or more child processes failed to terminate in a timely manner"
|
104
|
-
end
|
105
|
-
|
106
|
-
def kill(signal, pid)
|
107
|
-
Process.kill signal, pid
|
108
|
-
unless signal == 'KILL'
|
109
|
-
pid, status = Process.waitpid2 pid, 0
|
110
|
-
expect(status.exitstatus).to eq(0)
|
111
|
-
end
|
112
|
-
end
|
113
20
|
end
|
File without changes
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'integration/pubsub_examples'
|
3
|
+
|
4
|
+
if sidekiq_supported_ruby_version?
|
5
|
+
describe "sidekiq integration", integration: true do
|
6
|
+
include_examples 'pubsub', :sidekiq, :start_sidekiq_workers
|
7
|
+
|
8
|
+
def start_sidekiq_workers
|
9
|
+
@pids << fork do
|
10
|
+
ENV['REDIS_URL'] = @redis_url
|
11
|
+
ENV['REDIS_NAMESPACE'] = 'sidekiq'
|
12
|
+
|
13
|
+
exec 'bundle', 'exec', 'sidekiq',
|
14
|
+
'--concurrency', '1',
|
15
|
+
'--environment', 'test',
|
16
|
+
'--queue', 'app1',
|
17
|
+
'--queue', 'app2',
|
18
|
+
'--logfile', 'tmp/sidekiq-spec.log',
|
19
|
+
'--require', './spec/integration/setup/sidekiq.rb',
|
20
|
+
'--verbose'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -7,4 +7,8 @@ describe Chasqui::Broker do
|
|
7
7
|
it 'forwards events to subscriber queues' do
|
8
8
|
expect(-> { broker.forward_event }).to raise_error(NotImplementedError)
|
9
9
|
end
|
10
|
+
|
11
|
+
it 'does not use a redis namespace' do
|
12
|
+
expect(broker.redis).not_to respond_to(:namespace)
|
13
|
+
end
|
10
14
|
end
|
@@ -2,7 +2,12 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Chasqui::MultiBroker do
|
4
4
|
let(:broker) { Chasqui::MultiBroker.new }
|
5
|
-
|
5
|
+
|
6
|
+
before do
|
7
|
+
reset_chasqui
|
8
|
+
Chasqui.config.worker_backend = :resque
|
9
|
+
Resque.redis.namespace = nil
|
10
|
+
end
|
6
11
|
|
7
12
|
describe '#forward_event' do
|
8
13
|
before do
|
@@ -16,18 +21,18 @@ describe Chasqui::MultiBroker do
|
|
16
21
|
Chasqui.publish 'foo.bar', 'A'
|
17
22
|
broker.forward_event
|
18
23
|
|
19
|
-
expect(
|
24
|
+
expect(nnredis.llen(broker.inbox_queue)).to eq(0)
|
20
25
|
|
21
|
-
expect(
|
22
|
-
expect(
|
23
|
-
expect(
|
26
|
+
expect(nnredis.llen('queue:queue1')).to eq(1)
|
27
|
+
expect(nnredis.llen('queue:queue2')).to eq(0)
|
28
|
+
expect(nnredis.llen('queue:queue3')).to eq(1)
|
24
29
|
|
25
30
|
event = { 'event' => 'foo.bar', 'channel' => 'app1', 'data' => ['A'] }
|
26
31
|
|
27
|
-
job1 = JSON.parse
|
32
|
+
job1 = JSON.parse nnredis.lpop('queue:queue1')
|
28
33
|
expect(job1['args']).to include(event)
|
29
34
|
|
30
|
-
job3 = JSON.parse
|
35
|
+
job3 = JSON.parse nnredis.lpop('queue:queue3')
|
31
36
|
expect(job3['args']).to include(event)
|
32
37
|
end
|
33
38
|
|
@@ -40,7 +45,7 @@ describe Chasqui::MultiBroker do
|
|
40
45
|
Chasqui.config.channel = 'app2'
|
41
46
|
Chasqui.publish 'foo.bar', 'A'
|
42
47
|
|
43
|
-
job = JSON.parse
|
48
|
+
job = JSON.parse nnredis.blpop('queue:queue2')[1]
|
44
49
|
expect(job).to include('class' => 'Chasqui::Subscriber__queue2')
|
45
50
|
expect(job).to include('args' =>
|
46
51
|
[{ 'event' => 'foo.bar', 'channel' => 'app2', 'data' => ['A'] }])
|
@@ -57,17 +62,22 @@ describe Chasqui::MultiBroker do
|
|
57
62
|
allow(broker.redis).to receive(:smembers).and_raise(Redis::ConnectionError)
|
58
63
|
|
59
64
|
expect(-> { broker.forward_event }).to raise_error(Redis::ConnectionError)
|
60
|
-
expect(
|
61
|
-
expect(
|
65
|
+
expect(nnredis.llen('queue:queue2')).to eq(0)
|
66
|
+
expect(nnredis.llen(broker.in_progress_queue)).to eq(1)
|
62
67
|
|
63
68
|
allow(broker.redis).to receive(:smembers).and_call_original
|
64
69
|
broker.forward_event
|
65
|
-
expect(
|
70
|
+
expect(nnredis.llen('queue:queue2')).to eq(1)
|
66
71
|
|
67
|
-
job = JSON.parse
|
72
|
+
job = JSON.parse nnredis.lpop('queue:queue2')
|
68
73
|
expect(job['args']).to include(
|
69
74
|
'event' => 'foo', 'channel' => 'app2', 'data' => ['process'])
|
70
|
-
expect(
|
75
|
+
expect(nnredis.llen(broker.in_progress_queue)).to eq(0)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'works when queue is empty' do
|
79
|
+
Chasqui.config.broker_poll_interval = 1
|
80
|
+
expect(-> { broker.forward_event }).not_to raise_error
|
71
81
|
end
|
72
82
|
end
|
73
83
|
end
|
@@ -8,8 +8,9 @@ if sidekiq_supported_ruby_version?
|
|
8
8
|
describe '.create' do
|
9
9
|
it 'configures a new worker' do
|
10
10
|
worker_class = Chasqui::SidekiqWorker.create(subscriber)
|
11
|
-
expect(worker_class.
|
11
|
+
expect(worker_class.name).to eq('Chasqui::Subscriber__my_queue')
|
12
12
|
expect(worker_class.sidekiq_options).to include('queue' => 'my-queue')
|
13
|
+
expect(worker_class.included_modules).to include(Sidekiq::Worker)
|
13
14
|
expect(worker_class.new).to be_kind_of(Chasqui::SidekiqWorker)
|
14
15
|
end
|
15
16
|
end
|
data/spec/lib/chasqui_spec.rb
CHANGED
@@ -124,39 +124,6 @@ describe Chasqui do
|
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
127
|
-
describe '.subscribe' do
|
128
|
-
before { reset_chasqui }
|
129
|
-
|
130
|
-
it 'saves subscriptions' do
|
131
|
-
sub1 = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
|
132
|
-
sub2 = Chasqui.subscribe queue: 'app2-queue', channel: 'com.example.admin'
|
133
|
-
sub3 = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.video'
|
134
|
-
|
135
|
-
queues = Chasqui.redis.smembers "subscribers:com.example.admin"
|
136
|
-
expect(queues.sort).to eq(['app1-queue', 'app2-queue'])
|
137
|
-
|
138
|
-
queues = Chasqui.redis.smembers "subscribers:com.example.video"
|
139
|
-
expect(queues).to eq(['app1-queue'])
|
140
|
-
|
141
|
-
expect(Chasqui.subscriber('app1-queue')).to eq(sub1)
|
142
|
-
expect(Chasqui.subscriber('app2-queue')).to eq(sub2)
|
143
|
-
expect(sub1).to eq(sub3)
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'returns a subscriber' do
|
147
|
-
subscriber = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
|
148
|
-
expect(subscriber).to be_kind_of(Chasqui::Subscriber)
|
149
|
-
end
|
150
|
-
|
151
|
-
it 'yields a subscriber configuration context' do
|
152
|
-
$context = nil
|
153
|
-
Chasqui.subscribe queue: 'foo', channel: 'bar' do
|
154
|
-
$context = self
|
155
|
-
end
|
156
|
-
expect($context).to be_kind_of(Chasqui::Subscriber)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
127
|
describe '.create_worker' do
|
161
128
|
let(:subscriber) { j }
|
162
129
|
|
@@ -182,4 +149,70 @@ describe Chasqui do
|
|
182
149
|
end
|
183
150
|
end
|
184
151
|
end
|
152
|
+
|
153
|
+
describe '.subscribe' do
|
154
|
+
before do
|
155
|
+
reset_chasqui
|
156
|
+
Chasqui.config.worker_backend = :resque
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'resque worker subscriptions' do
|
160
|
+
before { Resque.redis.namespace = 'blah' }
|
161
|
+
|
162
|
+
it 'creates subscriptions using the appropriate redis namespace' do
|
163
|
+
sub1 = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
|
164
|
+
sub2 = Chasqui.subscribe queue: 'app2-queue', channel: 'com.example.admin'
|
165
|
+
sub3 = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.video'
|
166
|
+
|
167
|
+
queues = Chasqui.redis.smembers "subscribers:com.example.admin"
|
168
|
+
expect(queues.sort).to eq(['resque/blah:queue:app1-queue', 'resque/blah:queue:app2-queue'])
|
169
|
+
|
170
|
+
queues = Chasqui.redis.smembers "subscribers:com.example.video"
|
171
|
+
expect(queues).to eq(['resque/blah:queue:app1-queue'])
|
172
|
+
|
173
|
+
expect(Chasqui.subscriber('app1-queue')).to eq(sub1)
|
174
|
+
expect(Chasqui.subscriber('app2-queue')).to eq(sub2)
|
175
|
+
expect(sub1).to eq(sub3)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
if sidekiq_supported_ruby_version?
|
180
|
+
context 'sidekiq worker subscriptions' do
|
181
|
+
before do
|
182
|
+
Chasqui.config.worker_backend = :sidekiq
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'creates subscriptions using the appropriate redis namespace' do
|
186
|
+
Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
|
187
|
+
queues = Chasqui.redis.smembers "subscribers:com.example.admin"
|
188
|
+
expect(queues.sort).to eq(['sidekiq/queue:app1-queue'])
|
189
|
+
|
190
|
+
Sidekiq.redis = { url: redis.client.options[:url], namespace: 'foobar' }
|
191
|
+
Chasqui.subscribe queue: 'app2-queue', channel: 'com.example.video'
|
192
|
+
queues = Chasqui.redis.smembers "subscribers:com.example.video"
|
193
|
+
expect(queues.sort).to eq(['sidekiq/foobar:queue:app2-queue'])
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'returns a subscriber' do
|
199
|
+
subscriber = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
|
200
|
+
expect(subscriber).to be_kind_of(Chasqui::Subscriber)
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'yields a subscriber configuration context' do
|
204
|
+
$context = nil
|
205
|
+
Chasqui.subscribe queue: 'foo', channel: 'bar' do
|
206
|
+
$context = self
|
207
|
+
end
|
208
|
+
expect($context).to be_kind_of(Chasqui::Subscriber)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe '.subscriber_class_name' do
|
213
|
+
it 'transforms queue name into a subscribe class name' do
|
214
|
+
expect(Chasqui.subscriber_class_name('my-queue')).to eq(:Subscriber__my_queue)
|
215
|
+
expect(Chasqui.subscriber_class_name('queue:my-queue')).to eq(:Subscriber__my_queue)
|
216
|
+
end
|
217
|
+
end
|
185
218
|
end
|
@@ -7,15 +7,20 @@ module ChasquiSpecHelpers
|
|
7
7
|
|
8
8
|
def reset_config
|
9
9
|
Chasqui.instance_variable_set(:@config, nil)
|
10
|
-
Chasqui.config.logger =
|
10
|
+
Chasqui.config.logger = './tmp/test.log'
|
11
11
|
end
|
12
12
|
|
13
13
|
def redis
|
14
14
|
Chasqui.redis
|
15
15
|
end
|
16
16
|
|
17
|
+
def redis_no_namespace
|
18
|
+
redis.redis
|
19
|
+
end
|
20
|
+
alias nnredis redis_no_namespace
|
21
|
+
|
17
22
|
def flush_redis
|
18
|
-
|
23
|
+
nnredis.keys('*').each { |k| nnredis.del k }
|
19
24
|
end
|
20
25
|
|
21
26
|
end
|
data/spec/support/fake_logger.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chasqui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Bach
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -150,8 +150,13 @@ files:
|
|
150
150
|
- lib/chasqui/version.rb
|
151
151
|
- lib/chasqui/workers/resque_worker.rb
|
152
152
|
- lib/chasqui/workers/sidekiq_worker.rb
|
153
|
+
- lib/chasqui/workers/worker.rb
|
154
|
+
- spec/integration/pubsub_examples.rb
|
153
155
|
- spec/integration/resque_spec.rb
|
154
|
-
- spec/integration/
|
156
|
+
- spec/integration/setup/resque.rb
|
157
|
+
- spec/integration/setup/sidekiq.rb
|
158
|
+
- spec/integration/setup/subscribers.rb
|
159
|
+
- spec/integration/sidekiq_spec.rb
|
155
160
|
- spec/lib/chasqui/broker_spec.rb
|
156
161
|
- spec/lib/chasqui/cli_spec.rb
|
157
162
|
- spec/lib/chasqui/config_spec.rb
|
@@ -192,8 +197,12 @@ specification_version: 4
|
|
192
197
|
summary: Chasqui is a simple, lightweight, persistent implementation of the publish-subscribe
|
193
198
|
(pub/sub) messaging pattern for service oriented architectures.
|
194
199
|
test_files:
|
200
|
+
- spec/integration/pubsub_examples.rb
|
195
201
|
- spec/integration/resque_spec.rb
|
196
|
-
- spec/integration/
|
202
|
+
- spec/integration/setup/resque.rb
|
203
|
+
- spec/integration/setup/sidekiq.rb
|
204
|
+
- spec/integration/setup/subscribers.rb
|
205
|
+
- spec/integration/sidekiq_spec.rb
|
197
206
|
- spec/lib/chasqui/broker_spec.rb
|
198
207
|
- spec/lib/chasqui/cli_spec.rb
|
199
208
|
- spec/lib/chasqui/config_spec.rb
|