announcer 0.5.1
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 +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
|