chasqui 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -1
- data/Guardfile +14 -0
- data/README.md +6 -6
- data/Rakefile +16 -0
- data/bin/chasqui +6 -0
- data/chasqui.gemspec +6 -0
- data/examples/full.rb +4 -4
- data/lib/chasqui/broker.rb +70 -0
- data/lib/chasqui/cli.rb +78 -0
- data/lib/chasqui/config.rb +78 -0
- data/lib/chasqui/multi_broker.rb +56 -0
- data/lib/chasqui/subscriber.rb +74 -0
- data/lib/chasqui/version.rb +1 -1
- data/lib/chasqui/workers/resque_worker.rb +40 -0
- data/lib/chasqui/workers/sidekiq_worker.rb +25 -0
- data/lib/chasqui.rb +59 -21
- data/spec/integration/resque_spec.rb +113 -0
- data/spec/integration/subscribers.rb +14 -0
- data/spec/lib/chasqui/broker_spec.rb +10 -0
- data/spec/lib/chasqui/cli_spec.rb +74 -0
- data/spec/lib/chasqui/config_spec.rb +0 -0
- data/spec/lib/chasqui/multi_broker_spec.rb +73 -0
- data/spec/lib/chasqui/subscriber_spec.rb +67 -0
- data/spec/lib/chasqui/workers/resque_worker_spec.rb +27 -0
- data/spec/lib/chasqui/workers/sidekiq_worker_spec.rb +30 -0
- data/spec/lib/chasqui_spec.rb +185 -0
- data/spec/spec_helper.rb +5 -18
- data/spec/support/chasqui_spec_helpers.rb +21 -0
- data/spec/support/fake_logger.rb +5 -0
- data/spec/support/fake_subscriber.rb +12 -0
- data/spec/support/sidekiq_compatibility_check.rb +9 -0
- data/tmp/.keep +0 -0
- metadata +113 -6
- data/spec/chasqui_spec.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bb216d9f86e895b3a2c48b686a9d3d92f991cf8
|
4
|
+
data.tar.gz: e736ccd90ee44c1aa827c4d59e09136ea63f9958
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d174c6cb4f929a2135f86fe7bf4c06e32d8f66088e25d18fa4048b872aaaa9430888f7cb5f901665dff430064dd24772164fb1b19aa6b48f3b3e74f7fa9d14a2
|
7
|
+
data.tar.gz: e4e64b8ee8453138def77a8f229e3fb002f0e794d2c0e0013db5eaa12bdd6bae46e109d707e95b024a6dae4081dbdcfcf2ff7288ea4ea5c9f8ce4ba55eb19532
|
data/.travis.yml
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
2
|
+
require "guard/rspec/dsl"
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# RSpec files
|
6
|
+
rspec = dsl.rspec
|
7
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
8
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
9
|
+
watch(rspec.spec_files)
|
10
|
+
|
11
|
+
# Ruby files
|
12
|
+
ruby = dsl.ruby
|
13
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
14
|
+
end
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/jbgo/chasqui.svg?branch=master)](https://travis-ci.org/jbgo/chasqui)
|
2
|
+
|
1
3
|
# Chasqui
|
2
4
|
|
3
5
|
Chasqui is a simple, lightweight, persistent implementation of the publish-subscribe (pub/sub)
|
@@ -68,12 +70,12 @@ Publishing events is simple.
|
|
68
70
|
Chasqui.publish 'user.sign-up', user_id
|
69
71
|
```
|
70
72
|
|
71
|
-
To prevent conflicts with other applications, you can choose a unique
|
73
|
+
To prevent conflicts with other applications, you can choose a unique channel for your events.
|
72
74
|
|
73
75
|
```rb
|
74
76
|
# config/initializers/chasqui.rb
|
75
77
|
Chasqui.configure do |config|
|
76
|
-
config.
|
78
|
+
config.channel = 'com.example.myapp'
|
77
79
|
end
|
78
80
|
```
|
79
81
|
|
@@ -82,11 +84,9 @@ Now when you call `Chasqui.publish 'event.name', data, ...`, Chasqui will publis
|
|
82
84
|
|
83
85
|
## Subscribing to events
|
84
86
|
|
85
|
-
__NOT IMPLMENTED YET__
|
86
|
-
|
87
87
|
```rb
|
88
88
|
# file: otherapp/app/subscribers/user_events.rb
|
89
|
-
Chasqui.subscribe queue: 'unique_queue_name_for_app',
|
89
|
+
Chasqui.subscribe queue: 'unique_queue_name_for_app', channel: 'com.example.myapp' do
|
90
90
|
|
91
91
|
on 'user.sign-up' do |user_id|
|
92
92
|
user = User.find user_id
|
@@ -106,7 +106,7 @@ end
|
|
106
106
|
|
107
107
|
```rb
|
108
108
|
Chasqui.configure do |config|
|
109
|
-
config.
|
109
|
+
config.channel = 'com.example.transcoder'
|
110
110
|
config.redis = ENV.fetch('REDIS_URL')
|
111
111
|
config.workers = :sidekiq # or :resque
|
112
112
|
...
|
data/Rakefile
CHANGED
@@ -1,7 +1,23 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
|
+
require 'resque/tasks'
|
5
|
+
task 'resque:setup' => :environment
|
6
|
+
|
4
7
|
RSpec::Core::RakeTask.new(:spec)
|
5
8
|
|
6
9
|
task :default => :spec
|
7
10
|
|
11
|
+
task :environment do
|
12
|
+
$LOAD_PATH.unshift './lib'
|
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'
|
23
|
+
end
|
data/bin/chasqui
ADDED
data/chasqui.gemspec
CHANGED
@@ -17,7 +17,13 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
+
spec.add_dependency "redis"
|
21
|
+
spec.add_dependency "redis-namespace"
|
22
|
+
|
20
23
|
spec.add_development_dependency "bundler", "~> 1.7"
|
21
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
22
25
|
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "guard-rspec"
|
27
|
+
spec.add_development_dependency "resque"
|
28
|
+
spec.add_development_dependency "sidekiq"
|
23
29
|
end
|
data/examples/full.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# file: admin/config/initializers/chasqui.rb
|
2
2
|
Chasqui.configure do |config|
|
3
|
-
config.
|
3
|
+
config.channel = 'com.example.admin'
|
4
4
|
config.redis = ENV.fetch('REDIS_URL')
|
5
5
|
end
|
6
6
|
|
@@ -28,7 +28,7 @@ Chasqui.configure do |config|
|
|
28
28
|
end
|
29
29
|
|
30
30
|
# file: transcoder/app/subscribers/video_subscriber.rb
|
31
|
-
Chasqui.subscribe queue: 'transcoder.video',
|
31
|
+
Chasqui.subscribe queue: 'transcoder.video', channel: 'com.example.admin' do
|
32
32
|
on 'video.upload' do |video_id|
|
33
33
|
begin
|
34
34
|
Transcorder.transcode video_url(video_id)
|
@@ -41,14 +41,14 @@ Chasqui.subscribe queue: 'transcoder.video', namespace: 'com.example.admin' do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
# file: admin/app/subscribers/video_subscriber.rb
|
44
|
-
Chasqui.subscribe queue: 'admin.events',
|
44
|
+
Chasqui.subscribe queue: 'admin.events', channel: 'com.example.transcoder' do
|
45
45
|
|
46
46
|
on 'transcoder.video.complete' do |video_id|
|
47
47
|
video = Video.find video_id
|
48
48
|
VideoMailer.transcode_complete(video).deliver
|
49
49
|
end
|
50
50
|
|
51
|
-
on 'transcoder.video.
|
51
|
+
on 'transcoder.video.error' do |video_id, error|
|
52
52
|
video = Video.find video_id
|
53
53
|
VideoMailer.transcode_error(video, error).deliver
|
54
54
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
class Chasqui::Broker
|
4
|
+
attr_reader :config
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@config, :redis, :inbox, :logger
|
8
|
+
|
9
|
+
ShutdownSignals = %w(INT QUIT ABRT TERM).freeze
|
10
|
+
|
11
|
+
# To prevent unsuspecting clients from blocking forever, the broker uses
|
12
|
+
# it's own private redis connection.
|
13
|
+
def initialize
|
14
|
+
@shutdown_requested = nil
|
15
|
+
@config = Chasqui.config.dup
|
16
|
+
@config.redis = Redis.new @config.redis.client.options
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
install_signal_handlers
|
21
|
+
|
22
|
+
logger.info "broker started with pid #{Process.pid}"
|
23
|
+
logger.info "configured to fetch events from #{inbox} on #{redis.inspect}"
|
24
|
+
|
25
|
+
until_shutdown_requested { forward_event }
|
26
|
+
end
|
27
|
+
|
28
|
+
def forward_event
|
29
|
+
raise NotImplementedError.new "please define #forward_event in a subclass of #{self.class.name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def start
|
34
|
+
Chasqui::MultiBroker.new.start
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def install_signal_handlers
|
41
|
+
ShutdownSignals.each do |signal|
|
42
|
+
trap(signal) { @shutdown_requested = signal }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def until_shutdown_requested
|
47
|
+
catch :shutdown do
|
48
|
+
loop do
|
49
|
+
with_timeout do
|
50
|
+
if @shutdown_requested
|
51
|
+
logger.info "broker received signal, #@shutdown_requested. shutting down"
|
52
|
+
throw :shutdown
|
53
|
+
else
|
54
|
+
yield
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_timeout
|
62
|
+
# This timeout is a failsafe for an improperly configured broker
|
63
|
+
Timeout::timeout(config.broker_poll_interval + 1) do
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
rescue TimeoutError
|
67
|
+
logger.warn "broker poll interval exceeded for broker, #{self.class.name}"
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/chasqui/cli.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'chasqui'
|
4
|
+
|
5
|
+
class Chasqui::CLI
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@options, :logfile, :redis_url, :inbox_queue, :debug, :version
|
8
|
+
|
9
|
+
def initialize(argv)
|
10
|
+
build_options(argv)
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure
|
14
|
+
Chasqui.configure do |config|
|
15
|
+
config.logger = options.logfile if options.logfile
|
16
|
+
config.redis = options.redis_url if options.redis_url
|
17
|
+
config.inbox_queue = options.inbox_queue if options.inbox_queue
|
18
|
+
config.logger.level = Logger::DEBUG if options.debug
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
configure
|
24
|
+
|
25
|
+
if options.version
|
26
|
+
puts "chasqui #{Chasqui::VERSION}"
|
27
|
+
elsif options.help
|
28
|
+
puts @parser.help()
|
29
|
+
else
|
30
|
+
Chasqui::Broker.start
|
31
|
+
end
|
32
|
+
rescue => ex
|
33
|
+
Chasqui.logger.fatal ex.inspect
|
34
|
+
Chasqui.logger.fatal ex.backtrace.join("\n")
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def options
|
40
|
+
@options ||= build_options
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_options(argv)
|
44
|
+
opts = {}
|
45
|
+
|
46
|
+
@parser = OptionParser.new do |o|
|
47
|
+
o.banner = "Usage: #{argv[0]} [options]"
|
48
|
+
|
49
|
+
o.on('-f', '--logfile PATH', 'log file path') do |arg|
|
50
|
+
opts[:logfile] = arg
|
51
|
+
end
|
52
|
+
|
53
|
+
o.on('-r', '--redis URL', 'redis connection URL') do |arg|
|
54
|
+
opts[:redis_url] = arg
|
55
|
+
end
|
56
|
+
|
57
|
+
o.on('-q', '--inbox-queue NAME', 'name of the queue from which chasqui broker consumes events') do |arg|
|
58
|
+
opts[:inbox_queue] = arg
|
59
|
+
end
|
60
|
+
|
61
|
+
o.on('-d', '--debug', 'enable debug logging') do |arg|
|
62
|
+
opts[:debug] = arg
|
63
|
+
end
|
64
|
+
|
65
|
+
o.on('-v', '--version', 'show version and exit') do |arg|
|
66
|
+
opts[:version] = arg
|
67
|
+
end
|
68
|
+
|
69
|
+
o.on('-h', '--help', 'show this help pessage') do |arg|
|
70
|
+
opts[:help] = arg
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
@parser.parse!(argv)
|
75
|
+
@options = OpenStruct.new opts
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Chasqui
|
2
|
+
|
3
|
+
Defaults = {
|
4
|
+
inbox_queue: 'inbox',
|
5
|
+
redis_namespace: 'chasqui',
|
6
|
+
publish_channel: '__default',
|
7
|
+
broker_poll_interval: 3
|
8
|
+
}.freeze
|
9
|
+
|
10
|
+
class ConfigurationError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
CONFIG_SETTINGS = [
|
14
|
+
:broker_poll_interval,
|
15
|
+
:channel,
|
16
|
+
:inbox_queue,
|
17
|
+
:logger,
|
18
|
+
:redis,
|
19
|
+
:worker_backend
|
20
|
+
]
|
21
|
+
|
22
|
+
class Config < Struct.new(*CONFIG_SETTINGS)
|
23
|
+
def channel
|
24
|
+
self[:channel] ||= Defaults.fetch(:publish_channel)
|
25
|
+
end
|
26
|
+
|
27
|
+
def inbox_queue
|
28
|
+
self[:inbox_queue] ||= Defaults.fetch(:inbox_queue)
|
29
|
+
end
|
30
|
+
alias inbox inbox_queue
|
31
|
+
|
32
|
+
def redis
|
33
|
+
unless self[:redis]
|
34
|
+
self.redis = Redis.new
|
35
|
+
end
|
36
|
+
|
37
|
+
self[:redis]
|
38
|
+
end
|
39
|
+
|
40
|
+
def redis=(redis_config)
|
41
|
+
client = case redis_config
|
42
|
+
when Redis
|
43
|
+
redis_config
|
44
|
+
when String
|
45
|
+
Redis.new url: redis_config
|
46
|
+
else
|
47
|
+
Redis.new redis_config
|
48
|
+
end
|
49
|
+
|
50
|
+
self[:redis] = Redis::Namespace.new(Defaults.fetch(:redis_namespace), redis: client)
|
51
|
+
end
|
52
|
+
|
53
|
+
def logger
|
54
|
+
unless self[:logger]
|
55
|
+
self.logger = STDOUT
|
56
|
+
end
|
57
|
+
|
58
|
+
self[:logger]
|
59
|
+
end
|
60
|
+
|
61
|
+
def logger=(new_logger)
|
62
|
+
lg = if new_logger.respond_to? :info
|
63
|
+
new_logger
|
64
|
+
else
|
65
|
+
Logger.new(new_logger).tap do |lg|
|
66
|
+
lg.level = Logger::INFO
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
lg.progname = 'chasqui'
|
71
|
+
self[:logger] = lg
|
72
|
+
end
|
73
|
+
|
74
|
+
def broker_poll_interval
|
75
|
+
self[:broker_poll_interval] ||= Defaults.fetch(:broker_poll_interval)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Chasqui::MultiBroker < Chasqui::Broker
|
2
|
+
|
3
|
+
def forward_event
|
4
|
+
event = receive or return
|
5
|
+
queues = subscriber_queues event
|
6
|
+
|
7
|
+
redis.multi do
|
8
|
+
queues.each do |queue|
|
9
|
+
dispatch event, queue
|
10
|
+
end
|
11
|
+
redis.rpop(in_progress_queue)
|
12
|
+
end
|
13
|
+
|
14
|
+
logger.info "processed event: #{event['event']}, on channel: #{event['channel']}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def in_progress_queue
|
18
|
+
"#{inbox}:in_progress"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def receive
|
24
|
+
event = retry_failed_event || dequeue
|
25
|
+
logger.debug "received event: #{event['event']}, on channel: #{event['channel']}"
|
26
|
+
|
27
|
+
JSON.parse event
|
28
|
+
end
|
29
|
+
|
30
|
+
def retry_failed_event
|
31
|
+
redis.lrange(in_progress_queue, -1, -1).first.tap do |event|
|
32
|
+
unless event.nil?
|
33
|
+
logger.warn "detected failed event delivery, attempting recovery"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def dequeue
|
39
|
+
redis.brpoplpush(inbox, in_progress_queue, timeout: config.broker_poll_interval).tap do |event|
|
40
|
+
if event.nil?
|
41
|
+
logger.debug "reached timeout for broker poll interval: #{config.broker_poll_interval} seconds"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def dispatch(event, queue)
|
47
|
+
job = { class: "Chasqui::Subscriber__#{queue}", args: [event] }.to_json
|
48
|
+
logger.debug "dispatching event to queue: #{queue}, with job: #{job}"
|
49
|
+
redis.rpush "queue:#{queue}", job
|
50
|
+
end
|
51
|
+
|
52
|
+
def subscriber_queues(event)
|
53
|
+
redis.smembers "subscribers:#{event['channel']}"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Chasqui
|
4
|
+
|
5
|
+
HandlerAlreadyRegistered = Class.new StandardError
|
6
|
+
|
7
|
+
class Subscriber
|
8
|
+
attr_accessor :redis, :current_event
|
9
|
+
attr_reader :queue, :channel
|
10
|
+
|
11
|
+
def initialize(queue, channel)
|
12
|
+
@queue = queue
|
13
|
+
@channel = channel
|
14
|
+
end
|
15
|
+
|
16
|
+
def on(event_name, &block)
|
17
|
+
pattern = pattern_for_event event_name
|
18
|
+
|
19
|
+
if handler_patterns.include? pattern
|
20
|
+
raise HandlerAlreadyRegistered.new "handler already registered for event: #{event_name}"
|
21
|
+
else
|
22
|
+
handler_patterns << pattern
|
23
|
+
define_handler_method pattern, &block
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform(redis_for_worker, event)
|
28
|
+
self.redis = redis_for_worker
|
29
|
+
self.current_event = event
|
30
|
+
|
31
|
+
matching_handler_patterns_for(event['event']).each do |pattern|
|
32
|
+
call_handler pattern, *event['data']
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def matching_handler_patterns_for(event_name)
|
37
|
+
handler_patterns.select do |pattern|
|
38
|
+
pattern =~ event_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def call_handler(pattern, *args)
|
43
|
+
send "handler__#{pattern.to_s}", *args
|
44
|
+
end
|
45
|
+
|
46
|
+
def evaluate(&block)
|
47
|
+
@self_before_instance_eval = eval "self", block.binding
|
48
|
+
instance_eval &block
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def handler_patterns
|
54
|
+
@handler_patterns ||= Set.new
|
55
|
+
end
|
56
|
+
|
57
|
+
def method_missing(method, *args, &block)
|
58
|
+
if @self_before_instance_eval
|
59
|
+
@self_before_instance_eval.send method, *args, &block
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def pattern_for_event(event_name)
|
66
|
+
/\A#{event_name.to_s.downcase.gsub('*', '.*')}\z/
|
67
|
+
end
|
68
|
+
|
69
|
+
def define_handler_method(pattern, &block)
|
70
|
+
self.class.send :define_method, "handler__#{pattern.to_s}", &block
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
data/lib/chasqui/version.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
class Chasqui::ResqueWorker
|
2
|
+
|
3
|
+
class << self
|
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
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def find_or_build_worker(subscriber)
|
22
|
+
class_name = class_name_for subscriber
|
23
|
+
|
24
|
+
if Chasqui.const_defined? class_name
|
25
|
+
Chasqui.const_get class_name
|
26
|
+
else
|
27
|
+
Class.new(Chasqui::ResqueWorker).tap do |worker|
|
28
|
+
Chasqui.const_set class_name, worker
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def class_name_for(subscriber)
|
34
|
+
queue_name_constant = subscriber.queue.gsub(/[^\w]/, '_')
|
35
|
+
"Subscriber__#{queue_name_constant}".to_sym
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Chasqui::SidekiqWorker
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def create(subscriber)
|
6
|
+
Class.new(Chasqui::SidekiqWorker) do
|
7
|
+
include Sidekiq::Worker
|
8
|
+
sidekiq_options queue: subscriber.queue
|
9
|
+
@subscriber = subscriber
|
10
|
+
|
11
|
+
def perform(event)
|
12
|
+
self.class.subscriber.perform "TODO: redis", event
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.subscriber
|
18
|
+
@subscriber
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/chasqui.rb
CHANGED
@@ -1,39 +1,77 @@
|
|
1
|
-
require '
|
1
|
+
require 'forwardable'
|
2
2
|
require 'json'
|
3
|
+
require 'logger'
|
4
|
+
require 'redis'
|
5
|
+
require 'redis-namespace'
|
6
|
+
|
3
7
|
require "chasqui/version"
|
8
|
+
require "chasqui/config"
|
9
|
+
require "chasqui/broker"
|
10
|
+
require "chasqui/multi_broker"
|
11
|
+
require "chasqui/subscriber"
|
12
|
+
require "chasqui/workers/resque_worker"
|
13
|
+
require "chasqui/workers/sidekiq_worker"
|
4
14
|
|
5
15
|
module Chasqui
|
16
|
+
class << self
|
17
|
+
extend Forwardable
|
18
|
+
def_delegators :config, :redis, :channel, :inbox, :inbox_queue, :logger
|
6
19
|
|
7
|
-
|
20
|
+
def configure(&block)
|
21
|
+
@config ||= Config.new
|
22
|
+
yield @config
|
23
|
+
end
|
8
24
|
|
9
|
-
|
10
|
-
|
11
|
-
|
25
|
+
def config
|
26
|
+
@config ||= Config.new
|
27
|
+
end
|
12
28
|
|
13
|
-
|
14
|
-
|
15
|
-
|
29
|
+
def publish(event, *args)
|
30
|
+
payload = { event: event, channel: channel, data: args }
|
31
|
+
redis.lpush inbox_queue, payload.to_json
|
16
32
|
end
|
17
33
|
|
18
|
-
def
|
19
|
-
|
34
|
+
def subscribe(options={}, &block)
|
35
|
+
queue = options.fetch :queue
|
36
|
+
channel = options.fetch :channel
|
37
|
+
|
38
|
+
register_subscriber(queue, channel).tap do |sub|
|
39
|
+
sub.evaluate(&block) if block_given?
|
40
|
+
Chasqui::ResqueWorker.create sub
|
41
|
+
redis.sadd "subscribers:#{channel}", queue
|
42
|
+
end
|
20
43
|
end
|
21
44
|
|
22
|
-
def
|
23
|
-
|
45
|
+
def subscriber(queue)
|
46
|
+
subscribers[queue.to_s]
|
24
47
|
end
|
25
48
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
49
|
+
def create_worker(subscriber)
|
50
|
+
case config.worker_backend
|
51
|
+
when :resque
|
52
|
+
Chasqui::ResqueWorker.create subscriber
|
53
|
+
when :sidekiq
|
54
|
+
Chasqui::SidekiqWorker.create subscriber
|
55
|
+
else
|
56
|
+
raise ConfigurationError.new(
|
57
|
+
"Please choose a supported worker_backend. Choices: #{supported_worker_backends}")
|
58
|
+
end
|
29
59
|
end
|
30
60
|
|
31
|
-
|
32
|
-
|
33
|
-
|
61
|
+
private
|
62
|
+
|
63
|
+
def register_subscriber(queue, channel)
|
64
|
+
subscribers[queue.to_s] ||= Subscriber.new queue, channel
|
34
65
|
end
|
35
|
-
end
|
36
66
|
|
37
|
-
|
67
|
+
def subscribers
|
68
|
+
@subscribers ||= {}
|
69
|
+
end
|
70
|
+
|
71
|
+
SUPPORTED_WORKER_BACKENDS = [:resque, :sidekiq].freeze
|
38
72
|
|
39
|
-
|
73
|
+
def supported_worker_backends
|
74
|
+
SUPPORTED_WORKER_BACKENDS.join(', ')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|