announcer 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/config/defaults.yml +13 -0
- data/lib/ribbon/event_bus.rb +37 -0
- data/lib/ribbon/event_bus/config.rb +132 -0
- data/lib/ribbon/event_bus/errors.rb +55 -0
- data/lib/ribbon/event_bus/event.rb +93 -0
- data/lib/ribbon/event_bus/instance.rb +160 -0
- data/lib/ribbon/event_bus/mixins.rb +7 -0
- data/lib/ribbon/event_bus/mixins/has_config.rb +45 -0
- data/lib/ribbon/event_bus/mixins/has_instance.rb +13 -0
- data/lib/ribbon/event_bus/mixins/serializable.rb +183 -0
- data/lib/ribbon/event_bus/plugins.rb +6 -0
- data/lib/ribbon/event_bus/plugins/logging_plugin.rb +68 -0
- data/lib/ribbon/event_bus/plugins/plugin.rb +22 -0
- data/lib/ribbon/event_bus/publishers.rb +54 -0
- data/lib/ribbon/event_bus/publishers/async_resque_publisher.rb +81 -0
- data/lib/ribbon/event_bus/publishers/proc_publisher.rb +22 -0
- data/lib/ribbon/event_bus/publishers/publisher.rb +30 -0
- data/lib/ribbon/event_bus/publishers/remote_resque_publisher.rb +81 -0
- data/lib/ribbon/event_bus/publishers/resque_publisher.rb +87 -0
- data/lib/ribbon/event_bus/publishers/subscriptions_publisher.rb +8 -0
- data/lib/ribbon/event_bus/subscription.rb +165 -0
- data/lib/ribbon/event_bus/version.rb +5 -0
- metadata +212 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Ribbon::EventBus
|
2
|
+
module Mixins
|
3
|
+
module HasConfig
|
4
|
+
def self.included(base)
|
5
|
+
raise "HasConfig requires HasInstance" unless base < HasInstance
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def config_key(key)
|
11
|
+
config_keys << key.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
def config_keys(*keys)
|
15
|
+
unless keys.empty?
|
16
|
+
_has_config_values[:keys] = keys.map(&:to_sym)
|
17
|
+
else
|
18
|
+
_has_config_values[:keys] ||= _has_config_ancestor_keys
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def _has_config_ancestor_keys
|
23
|
+
ancestors[1] < HasConfig ? ancestors[1].config_keys.dup : []
|
24
|
+
end
|
25
|
+
|
26
|
+
def _has_config_values
|
27
|
+
@__has_config_values ||= {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def config
|
32
|
+
_has_config_config
|
33
|
+
end
|
34
|
+
|
35
|
+
def _has_config_config
|
36
|
+
@__has_config_config ||= _has_config_load_config.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
def _has_config_load_config
|
40
|
+
keys = self.class.config_keys
|
41
|
+
keys.inject(instance.config) { |c, k| c.send(k) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Ribbon::EventBus
|
4
|
+
module Mixins
|
5
|
+
module Serializable
|
6
|
+
MAGIC = :SRLZ
|
7
|
+
VERSION = 1
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def serialize_with(*args)
|
15
|
+
_serializable_values[:args] = args.map(&:to_sym)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(encoded)
|
19
|
+
marshalled = _serializable_decode(encoded)
|
20
|
+
package = _serializable_unmarshal(marshalled)
|
21
|
+
_serializable_load_package(package)
|
22
|
+
end
|
23
|
+
|
24
|
+
def _serializable_load_package(package)
|
25
|
+
klass, args = _serializable_unpackage(package)
|
26
|
+
klass._deserialize_args(args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def _deserialize_args(args)
|
30
|
+
args = args.map { |arg| _deserialize_arg(arg) }
|
31
|
+
|
32
|
+
if respond_to?(:load_from_serialized)
|
33
|
+
load_from_serialized(*args)
|
34
|
+
else
|
35
|
+
new(*args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def _deserialize_arg(arg)
|
40
|
+
if _serializable_valid_package?(arg, false)
|
41
|
+
_serializable_load_package(arg)
|
42
|
+
else
|
43
|
+
arg
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
###
|
48
|
+
# Encoding
|
49
|
+
###
|
50
|
+
def _serializable_encode(marshalled)
|
51
|
+
Base64.strict_encode64(marshalled) # => encoded
|
52
|
+
end
|
53
|
+
|
54
|
+
def _serializable_decode(encoded)
|
55
|
+
begin
|
56
|
+
Base64.strict_decode64(encoded) # => marshalled
|
57
|
+
rescue ArgumentError
|
58
|
+
raise Errors::SerializableError, 'serialized string not encoded properly'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
###
|
63
|
+
# Marshalling
|
64
|
+
###
|
65
|
+
def _serializable_marshal(package)
|
66
|
+
Marshal.dump(package) # => marshalled
|
67
|
+
end
|
68
|
+
|
69
|
+
def _serializable_unmarshal(marshalled)
|
70
|
+
begin
|
71
|
+
Marshal.load(marshalled) # => package
|
72
|
+
rescue TypeError
|
73
|
+
raise Errors::SerializableError, 'incorrect format'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
###
|
78
|
+
# Class Encoding
|
79
|
+
###
|
80
|
+
|
81
|
+
def _serializable_encode_class(klass)
|
82
|
+
klass.name.to_s.sub('Ribbon::EventBus::', '').to_sym
|
83
|
+
end
|
84
|
+
|
85
|
+
def _serializable_decode_class(encoded_klass)
|
86
|
+
Ribbon::EventBus.const_get(encoded_klass.to_s)
|
87
|
+
end
|
88
|
+
|
89
|
+
###
|
90
|
+
# Packaging
|
91
|
+
###
|
92
|
+
def _serializable_package(klass, args)
|
93
|
+
encoded_klass = _serializable_encode_class(klass)
|
94
|
+
[MAGIC, VERSION, encoded_klass] + args # => package
|
95
|
+
end
|
96
|
+
|
97
|
+
def _serializable_valid_package?(package, noisy=true)
|
98
|
+
unless package.is_a?(Array)
|
99
|
+
if noisy
|
100
|
+
raise Errors::SerializableError, 'not a package'
|
101
|
+
else
|
102
|
+
return false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
magic, version, class_name = package
|
107
|
+
|
108
|
+
# Check Magic
|
109
|
+
unless magic == MAGIC
|
110
|
+
if noisy
|
111
|
+
raise Errors::SerializableError, 'invalid serialized package'
|
112
|
+
else
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Check Version
|
118
|
+
unless version == VERSION
|
119
|
+
if noisy
|
120
|
+
raise Errors::SerializableError, 'unsupported package version'
|
121
|
+
else
|
122
|
+
return false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Check Class Name
|
127
|
+
unless class_name.is_a?(Symbol)
|
128
|
+
if noisy
|
129
|
+
raise Errors::SerializableError, 'invalid class name'
|
130
|
+
else
|
131
|
+
return false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
return true
|
136
|
+
end
|
137
|
+
|
138
|
+
def _serializable_unpackage(package)
|
139
|
+
_serializable_valid_package?(package)
|
140
|
+
magic, version, encoded_klass, *args = package
|
141
|
+
klass = _serializable_decode_class(encoded_klass)
|
142
|
+
[klass, args]
|
143
|
+
end
|
144
|
+
|
145
|
+
###
|
146
|
+
# Helpers
|
147
|
+
###
|
148
|
+
def _serializable_values
|
149
|
+
@__serializable_values ||= {}
|
150
|
+
end
|
151
|
+
|
152
|
+
def _serializable_args
|
153
|
+
_serializable_values[:args] or
|
154
|
+
raise Errors::SerializableError, "Missing serialize_with definition"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def serialize
|
159
|
+
package = _serializable_package
|
160
|
+
marshalled = self.class._serializable_marshal(package)
|
161
|
+
self.class._serializable_encode(marshalled)
|
162
|
+
end
|
163
|
+
|
164
|
+
def _serializable_package
|
165
|
+
args = _serializable_args.map { |arg| _serialize_arg(send(arg)) }
|
166
|
+
self.class._serializable_package(self.class, args)
|
167
|
+
end
|
168
|
+
|
169
|
+
def _serialize_arg(arg)
|
170
|
+
case arg
|
171
|
+
when Serializable
|
172
|
+
arg._serializable_package
|
173
|
+
else
|
174
|
+
arg
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def _serializable_args
|
179
|
+
self.class._serializable_args
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Ribbon::EventBus
|
4
|
+
module Plugins
|
5
|
+
class LoggingPlugin < Plugin
|
6
|
+
config_key :logging
|
7
|
+
|
8
|
+
def logger
|
9
|
+
@_logger ||= _load_logger
|
10
|
+
end
|
11
|
+
|
12
|
+
around_publish do |event|
|
13
|
+
_run('Publishing', event) { publish }
|
14
|
+
end
|
15
|
+
|
16
|
+
around_resque_publish do |event|
|
17
|
+
_run('Publishing on Resque', event) { resque_publish }
|
18
|
+
end
|
19
|
+
|
20
|
+
around_subscription do |sub, event|
|
21
|
+
_run('Executing Subscription', sub) { subscription }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def _load_logger
|
26
|
+
if config.logger?
|
27
|
+
config.logger
|
28
|
+
else
|
29
|
+
Logger.new(STDOUT).tap { |logger|
|
30
|
+
logger.level = _load_level
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def _load_level
|
36
|
+
case config.level
|
37
|
+
when :info, nil
|
38
|
+
Logger::INFO
|
39
|
+
when :warn
|
40
|
+
Logger::WARN
|
41
|
+
when :error
|
42
|
+
Logger::ERROR
|
43
|
+
when :fatal
|
44
|
+
Logger::FATAL
|
45
|
+
else
|
46
|
+
raise Errors::PluginError, "Invalid plugins.logging.level: #{config.level.inspect}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def _run(subject, object, &block)
|
51
|
+
logger.debug("#{subject}: #{object}")
|
52
|
+
|
53
|
+
if config.log_exceptions?
|
54
|
+
begin
|
55
|
+
block.call
|
56
|
+
rescue Exception => e
|
57
|
+
logger.fatal("Exception raised when #{subject.downcase} #{object}: #{e.inspect}")
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
else
|
61
|
+
block.call
|
62
|
+
end
|
63
|
+
|
64
|
+
logger.debug("Finished #{subject}: #{object}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'ribbon/plugins'
|
2
|
+
|
3
|
+
module Ribbon::EventBus
|
4
|
+
module Plugins
|
5
|
+
class Plugin < Ribbon::Plugins::Plugin
|
6
|
+
include Mixins::HasInstance
|
7
|
+
include Mixins::HasConfig
|
8
|
+
|
9
|
+
config_key :plugins
|
10
|
+
|
11
|
+
def initialize(plugins, params={})
|
12
|
+
super(plugins)
|
13
|
+
@instance = plugins.component
|
14
|
+
@_params = params || {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def config
|
18
|
+
@__config ||= super.merge_hash!(@_params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Ribbon::EventBus
|
2
|
+
module Publishers
|
3
|
+
autoload(:Publisher,
|
4
|
+
'ribbon/event_bus/publishers/publisher')
|
5
|
+
autoload(:ProcPublisher,
|
6
|
+
'ribbon/event_bus/publishers/proc_publisher')
|
7
|
+
autoload(:SubscriptionsPublisher,
|
8
|
+
'ribbon/event_bus/publishers/subscriptions_publisher')
|
9
|
+
autoload(:ResquePublisher,
|
10
|
+
'ribbon/event_bus/publishers/resque_publisher')
|
11
|
+
autoload(:RemoteResquePublisher,
|
12
|
+
'ribbon/event_bus/publishers/remote_resque_publisher')
|
13
|
+
autoload(:AsyncResquePublisher,
|
14
|
+
'ribbon/event_bus/publishers/async_resque_publisher')
|
15
|
+
|
16
|
+
module_function
|
17
|
+
def load_for_instance(instance)
|
18
|
+
config = instance.config
|
19
|
+
config.publish_to? ? _load_for_instance(instance, config.publish_to) : []
|
20
|
+
end
|
21
|
+
|
22
|
+
def _load_for_instance(instance, publishers)
|
23
|
+
publishers.map { |publisher| _load_with_args(publisher, instance) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def _load_with_args(publisher, *args)
|
27
|
+
case publisher
|
28
|
+
when Array
|
29
|
+
_load_with_args(publisher[0], *(args + publisher[1..-1]))
|
30
|
+
else
|
31
|
+
load(publisher).new(*args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def load(publisher)
|
36
|
+
case publisher
|
37
|
+
when String, Symbol then _load_from_string(publisher.to_s)
|
38
|
+
when Proc then _load_from_proc(publisher)
|
39
|
+
when Publisher then publisher
|
40
|
+
else raise Errors::InvalidPublisherError, publisher.inspect
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def _load_from_string(publisher_name)
|
45
|
+
const_get((publisher_name.split('_').map(&:capitalize) + ['Publisher']).join)
|
46
|
+
rescue NameError
|
47
|
+
raise Errors::InvalidPublisherNameError, publisher_name
|
48
|
+
end
|
49
|
+
|
50
|
+
def _load_from_proc(publisher_proc)
|
51
|
+
ProcPublisher.new(&publisher_proc)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'resque'
|
2
|
+
require 'redis'
|
3
|
+
require 'celluloid/current'
|
4
|
+
|
5
|
+
module Ribbon::EventBus
|
6
|
+
module Publishers
|
7
|
+
class AsyncResquePublisher < Publisher
|
8
|
+
config_key :resque
|
9
|
+
|
10
|
+
attr_reader :resque
|
11
|
+
|
12
|
+
def initialize(instance=nil, params={})
|
13
|
+
super
|
14
|
+
@resque = config.resque? ? config.resque : Resque
|
15
|
+
end
|
16
|
+
|
17
|
+
def worker_id
|
18
|
+
@__worker_id ||= "event_bus_resque_worker_#{object_id}".to_sym
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# The suprvisor created in the initializer will restart the PublisherWorker
|
23
|
+
# but there can be a short period of time when the actor returned by
|
24
|
+
# Celluloid::Actor[...] is dead. To avoid that we sleep for a millisecond
|
25
|
+
# to give it time to create a new worker thread. We try three times before
|
26
|
+
# giving up.
|
27
|
+
#
|
28
|
+
# This should ensure that it's unlikely for a dead worker to be returned.
|
29
|
+
# However, if a dead worker is returned, then async calls will silently
|
30
|
+
# fail, allowing normal execution. This makes firing events best-effort.
|
31
|
+
def worker
|
32
|
+
# Retrieve the PublisherWorker or start the supervisor.
|
33
|
+
w = Celluloid::Actor[worker_id] || PublisherWorker.supervise(
|
34
|
+
args: [self],
|
35
|
+
as: worker_id
|
36
|
+
).send(worker_id)
|
37
|
+
|
38
|
+
3.times {
|
39
|
+
if w.dead?
|
40
|
+
sleep(0.001)
|
41
|
+
w = Celluloid::Actor[worker_id]
|
42
|
+
else
|
43
|
+
break
|
44
|
+
end
|
45
|
+
}
|
46
|
+
|
47
|
+
w
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Needs to exist for the ResquePublisher::PublisherJob to succeed.
|
52
|
+
def subscription_queue_formatter
|
53
|
+
ResquePublisher.subscription_queue_formatter(config)
|
54
|
+
end
|
55
|
+
|
56
|
+
def publish(event)
|
57
|
+
super
|
58
|
+
worker.async.publish(event) unless event.subscriptions.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
class PublisherWorker
|
62
|
+
include Celluloid
|
63
|
+
|
64
|
+
attr_reader :publisher
|
65
|
+
|
66
|
+
def initialize(publisher)
|
67
|
+
@publisher = publisher
|
68
|
+
end
|
69
|
+
|
70
|
+
def publish(event)
|
71
|
+
publisher.resque.enqueue_to(
|
72
|
+
publisher.config.publisher_queue.to_sym,
|
73
|
+
ResquePublisher::PublisherJob,
|
74
|
+
event.serialize,
|
75
|
+
:async_resque
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end # PublisherWorker
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|