chasqui 0.1.0 → 0.8.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 +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
|