queue-bus 0.6.0 → 0.9.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 +4 -4
- data/.circleci/config.yml +21 -0
- data/.rubocop.yml +35 -0
- data/CHANGELOG.md +38 -0
- data/Gemfile +4 -2
- data/README.mdown +16 -0
- data/Rakefile +2 -0
- data/lib/queue-bus.rb +15 -13
- data/lib/queue_bus/adapters/base.rb +4 -2
- data/lib/queue_bus/adapters/data.rb +12 -11
- data/lib/queue_bus/application.rb +13 -15
- data/lib/queue_bus/config.rb +64 -66
- data/lib/queue_bus/dispatch.rb +14 -12
- data/lib/queue_bus/dispatchers.rb +12 -5
- data/lib/queue_bus/driver.rb +15 -10
- data/lib/queue_bus/heartbeat.rb +32 -30
- data/lib/queue_bus/local.rb +9 -9
- data/lib/queue_bus/matcher.rb +36 -27
- data/lib/queue_bus/publisher.rb +7 -5
- data/lib/queue_bus/publishing.rb +31 -24
- data/lib/queue_bus/rider.rb +26 -22
- data/lib/queue_bus/subscriber.rb +20 -14
- data/lib/queue_bus/subscription.rb +25 -15
- data/lib/queue_bus/subscription_list.rb +30 -12
- data/lib/queue_bus/task_manager.rb +18 -16
- data/lib/queue_bus/tasks.rb +27 -11
- data/lib/queue_bus/util.rb +11 -8
- data/lib/queue_bus/version.rb +3 -1
- data/lib/queue_bus/worker.rb +3 -2
- data/queue-bus.gemspec +19 -17
- data/spec/adapter/publish_at_spec.rb +28 -25
- data/spec/adapter/support.rb +7 -1
- data/spec/adapter_spec.rb +4 -2
- data/spec/application_spec.rb +97 -97
- data/spec/config_spec.rb +116 -40
- data/spec/dispatch_spec.rb +48 -51
- data/spec/driver_spec.rb +60 -58
- data/spec/heartbeat_spec.rb +26 -24
- data/spec/integration_spec.rb +41 -40
- data/spec/matcher_spec.rb +104 -102
- data/spec/publish_spec.rb +46 -46
- data/spec/publisher_spec.rb +3 -1
- data/spec/rider_spec.rb +16 -14
- data/spec/spec_helper.rb +12 -7
- data/spec/subscriber_spec.rb +227 -227
- data/spec/subscription_list_spec.rb +31 -31
- data/spec/subscription_spec.rb +37 -36
- data/spec/worker_spec.rb +17 -15
- metadata +21 -6
data/lib/queue_bus/dispatch.rb
CHANGED
@@ -1,23 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Creates a DSL for apps to define their blocks to run for event_types
|
2
4
|
|
3
5
|
module QueueBus
|
6
|
+
# A Dispatch object can be used to declare an application along with it's various subscriptions.
|
4
7
|
class Dispatch
|
5
|
-
|
6
8
|
attr_reader :app_key, :subscriptions
|
7
|
-
|
9
|
+
|
8
10
|
def initialize(app_key)
|
9
11
|
@app_key = Application.normalize(app_key)
|
10
12
|
@subscriptions = SubscriptionList.new
|
11
13
|
end
|
12
|
-
|
14
|
+
|
13
15
|
def size
|
14
16
|
@subscriptions.size
|
15
17
|
end
|
16
|
-
|
18
|
+
|
17
19
|
def subscribe(key, matcher_hash = nil, &block)
|
18
|
-
dispatch_event(
|
20
|
+
dispatch_event('default', key, matcher_hash, block)
|
19
21
|
end
|
20
|
-
|
22
|
+
|
21
23
|
# allows definitions of other queues
|
22
24
|
def method_missing(method_name, *args, &block)
|
23
25
|
if args.size == 1 && block
|
@@ -28,7 +30,7 @@ module QueueBus
|
|
28
30
|
super
|
29
31
|
end
|
30
32
|
end
|
31
|
-
|
33
|
+
|
32
34
|
def execute(key, attributes)
|
33
35
|
sub = subscriptions.key(key)
|
34
36
|
if sub
|
@@ -41,17 +43,17 @@ module QueueBus
|
|
41
43
|
def subscription_matches(attributes)
|
42
44
|
out = subscriptions.matches(attributes)
|
43
45
|
out.each do |sub|
|
44
|
-
sub.app_key =
|
46
|
+
sub.app_key = app_key
|
45
47
|
end
|
46
48
|
out
|
47
49
|
end
|
48
|
-
|
50
|
+
|
49
51
|
def dispatch_event(queue, key, matcher_hash, block)
|
50
52
|
# if not matcher_hash, assume key is a event_type regex
|
51
|
-
matcher_hash ||= {
|
52
|
-
add_subscription("#{app_key}_#{queue}", key,
|
53
|
+
matcher_hash ||= { 'bus_event_type' => key }
|
54
|
+
add_subscription("#{app_key}_#{queue}", key, '::QueueBus::Rider', matcher_hash, block)
|
53
55
|
end
|
54
|
-
|
56
|
+
|
55
57
|
def add_subscription(queue_name, key, class_name, matcher_hash = nil, block)
|
56
58
|
sub = Subscription.register(queue_name, key, class_name, matcher_hash, block)
|
57
59
|
subscriptions.add(sub)
|
@@ -1,26 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module QueueBus
|
4
|
+
# A collection of Dispatches
|
5
|
+
#
|
6
|
+
# Each Dispatch is an application with it's own set of subscriptions. This is a master object
|
7
|
+
# that provides some basic controls over the set of applications.
|
2
8
|
class Dispatchers
|
3
|
-
|
9
|
+
# Fetches a dispatch for the application key and binds the provided block to it.
|
10
|
+
def dispatch(app_key = nil, &block)
|
4
11
|
dispatcher = dispatcher_by_key(app_key)
|
5
12
|
dispatcher.instance_eval(&block)
|
6
13
|
dispatcher
|
7
14
|
end
|
8
|
-
|
15
|
+
|
9
16
|
def dispatchers
|
10
17
|
@dispatchers ||= {}
|
11
18
|
@dispatchers.values
|
12
19
|
end
|
13
|
-
|
20
|
+
|
14
21
|
def dispatcher_by_key(app_key)
|
15
22
|
app_key = Application.normalize(app_key || ::QueueBus.default_app_key)
|
16
23
|
@dispatchers ||= {}
|
17
24
|
@dispatchers[app_key] ||= Dispatch.new(app_key)
|
18
25
|
end
|
19
|
-
|
26
|
+
|
20
27
|
def dispatcher_execute(app_key, key, attributes)
|
21
28
|
@dispatchers ||= {}
|
22
29
|
dispatcher = @dispatchers[app_key]
|
23
|
-
dispatcher
|
30
|
+
dispatcher&.execute(key, attributes)
|
24
31
|
end
|
25
32
|
end
|
26
33
|
end
|
data/lib/queue_bus/driver.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module QueueBus
|
2
|
-
#
|
4
|
+
# Fans out an event to multiple queues
|
5
|
+
#
|
6
|
+
# When a single event is broadcast, it may have zero to many subscriptions attached to it.
|
7
|
+
# The Driver is what is run in order to look up the subscription matches and enqueue each
|
8
|
+
# of the jobs. It uses the class_name supplied by the subscription to know which class will
|
9
|
+
# be performed.
|
3
10
|
class Driver
|
4
|
-
|
5
11
|
class << self
|
6
12
|
def subscription_matches(attributes)
|
7
13
|
out = []
|
@@ -12,24 +18,23 @@ module QueueBus
|
|
12
18
|
out
|
13
19
|
end
|
14
20
|
|
15
|
-
def perform(attributes={})
|
16
|
-
raise
|
21
|
+
def perform(attributes = {})
|
22
|
+
raise 'No attributes passed' if attributes.empty?
|
17
23
|
|
18
24
|
::QueueBus.log_worker("Driver running: #{attributes.inspect}")
|
19
25
|
|
20
26
|
subscription_matches(attributes).each do |sub|
|
21
27
|
::QueueBus.log_worker(" ...sending to #{sub.queue_name} queue with class #{sub.class_name} for app #{sub.app_key} because of subscription: #{sub.key}")
|
22
28
|
|
23
|
-
bus_attr = {
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
29
|
+
bus_attr = { 'bus_driven_at' => Time.now.to_i,
|
30
|
+
'bus_rider_queue' => sub.queue_name,
|
31
|
+
'bus_rider_app_key' => sub.app_key,
|
32
|
+
'bus_rider_sub_key' => sub.key,
|
33
|
+
'bus_rider_class_name' => sub.class_name }
|
28
34
|
bus_attr = bus_attr.merge(attributes || {})
|
29
35
|
::QueueBus.enqueue_to(sub.queue_name, sub.class_name, bus_attr)
|
30
36
|
end
|
31
37
|
end
|
32
38
|
end
|
33
|
-
|
34
39
|
end
|
35
40
|
end
|
data/lib/queue_bus/heartbeat.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module QueueBus
|
2
|
-
#
|
4
|
+
# When run, will calculate all of the heartbeats that need to be sent and then broadcasts
|
5
|
+
# those events out for execution. By always backfilling it ensures that no heartbeat is
|
6
|
+
# ever missed.
|
3
7
|
class Heartbeat
|
4
|
-
|
5
8
|
class << self
|
6
|
-
|
7
9
|
def lock_key
|
8
|
-
|
10
|
+
'bus:heartbeat:lock'
|
9
11
|
end
|
10
12
|
|
11
13
|
def lock_seconds
|
@@ -38,71 +40,71 @@ module QueueBus
|
|
38
40
|
::QueueBus.redis { |redis| redis.del(lock_key) }
|
39
41
|
end
|
40
42
|
|
41
|
-
|
42
43
|
def redis_key
|
43
|
-
|
44
|
+
'bus:heartbeat:timestamp'
|
44
45
|
end
|
45
46
|
|
46
47
|
def environment_name
|
47
|
-
ENV[
|
48
|
+
ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['BUS_ENV']
|
48
49
|
end
|
49
50
|
|
50
51
|
def get_saved_minute!
|
51
52
|
key = ::QueueBus.redis { |redis| redis.get(redis_key) }
|
52
53
|
return nil if key.nil?
|
54
|
+
|
53
55
|
case environment_name
|
54
56
|
when 'development', 'test'
|
55
57
|
# only 3 minutes in development; otherwise, TONS of events if not run in a while
|
56
|
-
three_ago = Time.now.to_i - 3
|
58
|
+
three_ago = Time.now.to_i / 60 - 3
|
57
59
|
key = three_ago if key.to_i < three_ago
|
58
60
|
end
|
59
|
-
|
61
|
+
key.to_i
|
60
62
|
end
|
61
63
|
|
62
64
|
def set_saved_minute!(epoch_minute)
|
63
65
|
::QueueBus.redis { |redis| redis.set(redis_key, epoch_minute) }
|
64
66
|
end
|
65
67
|
|
66
|
-
def perform(*
|
68
|
+
def perform(*_args)
|
67
69
|
real_now = Time.now.to_i
|
68
70
|
run_until = lock! - 2
|
69
71
|
return if run_until < real_now
|
70
72
|
|
71
|
-
while(
|
73
|
+
while (real_now = Time.now.to_i) < run_until
|
72
74
|
minutes = real_now.to_i / 60
|
73
75
|
last = get_saved_minute!
|
74
76
|
if last
|
75
77
|
break if minutes <= last
|
78
|
+
|
76
79
|
minutes = last + 1
|
77
80
|
end
|
78
81
|
|
79
|
-
seconds = minutes *
|
80
|
-
hours = minutes /
|
81
|
-
days = minutes / (60*24)
|
82
|
+
seconds = minutes * 60
|
83
|
+
hours = minutes / 60
|
84
|
+
days = minutes / (60 * 24)
|
82
85
|
|
83
|
-
now
|
86
|
+
now = Time.at(seconds)
|
84
87
|
|
85
88
|
attributes = {}
|
86
|
-
attributes[
|
87
|
-
attributes[
|
88
|
-
attributes[
|
89
|
-
attributes[
|
90
|
-
|
91
|
-
attributes[
|
92
|
-
attributes[
|
93
|
-
attributes[
|
94
|
-
attributes[
|
95
|
-
attributes[
|
96
|
-
attributes[
|
97
|
-
attributes[
|
98
|
-
|
99
|
-
::QueueBus.publish(
|
89
|
+
attributes['epoch_seconds'] = seconds
|
90
|
+
attributes['epoch_minutes'] = minutes
|
91
|
+
attributes['epoch_hours'] = hours
|
92
|
+
attributes['epoch_days'] = days
|
93
|
+
|
94
|
+
attributes['minute'] = now.min
|
95
|
+
attributes['hour'] = now.hour
|
96
|
+
attributes['day'] = now.day
|
97
|
+
attributes['month'] = now.month
|
98
|
+
attributes['year'] = now.year
|
99
|
+
attributes['yday'] = now.yday
|
100
|
+
attributes['wday'] = now.wday
|
101
|
+
|
102
|
+
::QueueBus.publish('heartbeat_minutes', attributes)
|
100
103
|
set_saved_minute!(minutes)
|
101
104
|
end
|
102
105
|
|
103
106
|
unlock!
|
104
107
|
end
|
105
108
|
end
|
106
|
-
|
107
109
|
end
|
108
110
|
end
|
data/lib/queue_bus/local.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module QueueBus
|
2
4
|
# only process local queues
|
3
5
|
class Local
|
4
|
-
|
5
6
|
class << self
|
6
7
|
def publish(attributes = {})
|
7
8
|
if ::QueueBus.local_mode == :suppress
|
8
9
|
::QueueBus.log_worker("Suppressed: #{attributes.inspect}")
|
9
|
-
return
|
10
|
+
return # not doing anything
|
10
11
|
end
|
11
12
|
|
12
13
|
# To json and back to simlulate enqueueing
|
@@ -17,15 +18,15 @@ module QueueBus
|
|
17
18
|
|
18
19
|
# looking for subscriptions, not queues
|
19
20
|
subscription_matches(attributes).each do |sub|
|
20
|
-
bus_attr = {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
bus_attr = { 'bus_driven_at' => Time.now.to_i,
|
22
|
+
'bus_rider_queue' => sub.queue_name,
|
23
|
+
'bus_rider_app_key' => sub.app_key,
|
24
|
+
'bus_rider_sub_key' => sub.key,
|
25
|
+
'bus_rider_class_name' => sub.class_name }
|
25
26
|
to_publish = bus_attr.merge(attributes || {})
|
26
27
|
if ::QueueBus.local_mode == :standalone
|
27
28
|
::QueueBus.enqueue_to(sub.queue_name, sub.class_name, bus_attr.merge(attributes || {}))
|
28
|
-
else
|
29
|
+
else # defaults to inline mode
|
29
30
|
sub.execute!(to_publish)
|
30
31
|
end
|
31
32
|
end
|
@@ -41,6 +42,5 @@ module QueueBus
|
|
41
42
|
out
|
42
43
|
end
|
43
44
|
end
|
44
|
-
|
45
45
|
end
|
46
46
|
end
|
data/lib/queue_bus/matcher.rb
CHANGED
@@ -1,70 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module QueueBus
|
4
|
+
# Tests whether a field on a bus event matches a filter.
|
2
5
|
class Matcher
|
3
|
-
SPECIAL_PREPEND =
|
6
|
+
SPECIAL_PREPEND = 'bus_special_value_'
|
4
7
|
attr_reader :filters
|
5
8
|
def initialize(hash)
|
6
9
|
@filters = encode(hash)
|
7
10
|
end
|
8
|
-
|
11
|
+
|
9
12
|
def to_redis
|
10
13
|
@filters
|
11
14
|
end
|
12
|
-
|
15
|
+
|
13
16
|
def match?(attribute_name, attributes)
|
14
17
|
mine = filters[attribute_name].to_s
|
15
|
-
return false if mine.
|
16
|
-
|
18
|
+
return false if mine.empty?
|
19
|
+
|
17
20
|
given = attributes[attribute_name]
|
18
21
|
case mine
|
19
22
|
when "#{SPECIAL_PREPEND}key"
|
20
|
-
return true if attributes.
|
23
|
+
return true if attributes.key?(attribute_name)
|
24
|
+
|
21
25
|
return false
|
22
26
|
when "#{SPECIAL_PREPEND}blank"
|
23
|
-
return true if given.to_s.strip.
|
27
|
+
return true if given.to_s.strip.empty?
|
28
|
+
|
24
29
|
return false
|
25
30
|
when "#{SPECIAL_PREPEND}empty"
|
26
|
-
return false if given
|
27
|
-
return true if given.to_s.
|
31
|
+
return false if given.nil?
|
32
|
+
return true if given.to_s.empty?
|
33
|
+
|
28
34
|
return false
|
29
35
|
when "#{SPECIAL_PREPEND}nil"
|
30
|
-
return true if given
|
36
|
+
return true if given.nil?
|
37
|
+
|
31
38
|
return false
|
32
39
|
when "#{SPECIAL_PREPEND}value"
|
33
|
-
return false if given
|
40
|
+
return false if given.nil?
|
41
|
+
|
34
42
|
return true
|
35
43
|
when "#{SPECIAL_PREPEND}present"
|
36
|
-
return true
|
44
|
+
return true unless given.to_s.strip.empty?
|
45
|
+
|
37
46
|
return false
|
38
47
|
end
|
39
|
-
|
48
|
+
|
40
49
|
given = given.to_s
|
41
|
-
|
50
|
+
|
42
51
|
return true if mine == given
|
52
|
+
|
43
53
|
begin
|
44
54
|
# if it's already a regex, don't mess with it
|
45
55
|
# otherwise, it should have start and end line situation
|
46
|
-
if mine[0..6] ==
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
56
|
+
regex = if mine[0..6] == '(?-mix:'
|
57
|
+
Regexp.new(mine)
|
58
|
+
else
|
59
|
+
Regexp.new("^#{mine}$")
|
60
|
+
end
|
51
61
|
return !!regex.match(given)
|
52
|
-
rescue
|
62
|
+
rescue StandardError
|
53
63
|
return false
|
54
64
|
end
|
55
65
|
end
|
56
|
-
|
66
|
+
|
57
67
|
def matches?(attributes)
|
58
68
|
return false if filters.empty?
|
59
|
-
return false if attributes
|
60
|
-
|
69
|
+
return false if attributes.nil?
|
70
|
+
|
61
71
|
filters.keys.each do |key|
|
62
72
|
return false unless match?(key, attributes)
|
63
73
|
end
|
64
|
-
|
74
|
+
|
65
75
|
true
|
66
76
|
end
|
67
|
-
|
77
|
+
|
68
78
|
def encode(hash)
|
69
79
|
out = {}
|
70
80
|
hash.each do |key, value|
|
@@ -73,9 +83,8 @@ module QueueBus
|
|
73
83
|
value = "#{SPECIAL_PREPEND}#{value}"
|
74
84
|
end
|
75
85
|
out[key.to_s] = value.to_s
|
76
|
-
end
|
86
|
+
end
|
77
87
|
out
|
78
88
|
end
|
79
89
|
end
|
80
90
|
end
|
81
|
-
|
data/lib/queue_bus/publisher.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module QueueBus
|
2
|
-
#
|
4
|
+
# A simple publishing worker for QueueBus. Makes publishing asynchronously possible since
|
5
|
+
# it may be enqueued to the background worker with a delay. This will allow the event to
|
6
|
+
# be published at a later time.
|
3
7
|
class Publisher
|
4
|
-
|
5
8
|
class << self
|
6
9
|
def perform(attributes)
|
7
|
-
event_type = attributes[
|
10
|
+
event_type = attributes['bus_event_type']
|
8
11
|
::QueueBus.log_worker("Publisher running: #{event_type} - #{attributes.inspect}")
|
9
12
|
::QueueBus.publish(event_type, attributes)
|
10
13
|
end
|
11
14
|
end
|
12
|
-
|
13
15
|
end
|
14
|
-
end
|
16
|
+
end
|