queue-bus 0.9.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -0
  3. data/CHANGELOG.md +26 -0
  4. data/Gemfile +4 -2
  5. data/README.mdown +15 -3
  6. data/Rakefile +2 -0
  7. data/lib/queue-bus.rb +16 -12
  8. data/lib/queue_bus/adapters/base.rb +4 -2
  9. data/lib/queue_bus/adapters/data.rb +12 -11
  10. data/lib/queue_bus/application.rb +57 -22
  11. data/lib/queue_bus/config.rb +22 -1
  12. data/lib/queue_bus/dispatch.rb +14 -12
  13. data/lib/queue_bus/dispatchers.rb +12 -5
  14. data/lib/queue_bus/driver.rb +15 -10
  15. data/lib/queue_bus/heartbeat.rb +32 -30
  16. data/lib/queue_bus/local.rb +9 -9
  17. data/lib/queue_bus/matcher.rb +36 -27
  18. data/lib/queue_bus/publisher.rb +7 -5
  19. data/lib/queue_bus/publishing.rb +32 -24
  20. data/lib/queue_bus/rider.rb +26 -22
  21. data/lib/queue_bus/subscriber.rb +20 -14
  22. data/lib/queue_bus/subscription.rb +25 -15
  23. data/lib/queue_bus/subscription_list.rb +30 -12
  24. data/lib/queue_bus/task_manager.rb +25 -16
  25. data/lib/queue_bus/tasks.rb +28 -15
  26. data/lib/queue_bus/util.rb +11 -8
  27. data/lib/queue_bus/version.rb +3 -1
  28. data/lib/queue_bus/worker.rb +3 -2
  29. data/queue-bus.gemspec +19 -18
  30. data/spec/adapter/publish_at_spec.rb +28 -25
  31. data/spec/adapter/support.rb +7 -1
  32. data/spec/adapter_spec.rb +4 -2
  33. data/spec/application_spec.rb +138 -96
  34. data/spec/config_spec.rb +35 -0
  35. data/spec/dispatch_spec.rb +48 -51
  36. data/spec/driver_spec.rb +60 -58
  37. data/spec/heartbeat_spec.rb +26 -24
  38. data/spec/integration_spec.rb +41 -40
  39. data/spec/matcher_spec.rb +104 -102
  40. data/spec/publish_spec.rb +68 -46
  41. data/spec/publisher_spec.rb +3 -1
  42. data/spec/rider_spec.rb +16 -14
  43. data/spec/spec_helper.rb +2 -2
  44. data/spec/subscriber_spec.rb +227 -227
  45. data/spec/subscription_list_spec.rb +57 -31
  46. data/spec/subscription_spec.rb +37 -36
  47. data/spec/worker_spec.rb +17 -15
  48. metadata +7 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9002e9de95db1dfd0ed4cc3ba4196dd3d10fd46b3c2e568e12af7b0d0aa75306
4
- data.tar.gz: '079f6ce144775b86c7f1e2214595a9e87e984f80f6636a2f9923f8c574d9bb83'
3
+ metadata.gz: 329cc0a74e428d99421a14c9b2b07e39e14ee98da5c43babceb51376d27006c6
4
+ data.tar.gz: 2439e577df8ea33e2f959b49ddfc105858844080556c4431a47897667d87fa92
5
5
  SHA512:
6
- metadata.gz: 1903f31f88bda747999ac8cefc2e2a8f60bafe06225d9fa3ea7b12ffee16c6e09ffcd448494edf15dd8f81d5d9576db97c815dcd78ae6526e94a487a7e3d2e40
7
- data.tar.gz: a9b711c88a06812acd66210633cda67e2af46abf8db3960ba184fc54e078de2c07f85e36884317c48eb7820ed9fa66e4b8432b99edbf787e8246f48039f101c4
6
+ metadata.gz: 2875311c4100923fc00886ac05e2b4053ec6bfff442114faa6cc6bf39698916234866f57ebcbeb09dbcaca85484ca3bb0ce88cdbf8eb5640f6ca3b46d88a5a23
7
+ data.tar.gz: f7d88adffc92f2635b1068f47c68238787b55eea86d4302bc2579a40bc2c3ff4ce145d62688a7584a2b48fa31b03951b993757a0e3b65eb71a1b3a4715aeb741
@@ -0,0 +1,21 @@
1
+ version: 2.1
2
+ orbs:
3
+ ruby: circleci/ruby@0.1.2
4
+
5
+ jobs:
6
+ build:
7
+ docker:
8
+ - image: circleci/ruby:2.6.3-stretch-node
9
+ - image: circleci/redis:4.0.12-alpine
10
+ executor: ruby/default
11
+ steps:
12
+ - checkout
13
+ - run:
14
+ name: Which bundler?
15
+ command: bundle -v
16
+ - ruby/bundle-install
17
+ - run:
18
+ name: Run tests
19
+ environment:
20
+ REDIS_URL: redis://127.0.0.1:6379
21
+ command: bundle exec rspec spec
data/CHANGELOG.md CHANGED
@@ -6,6 +6,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.12.0]
10
+
11
+ ### Changed
12
+ - Pipelines fetching all queue subscriptions when using `QueueBus::Application.all`
13
+
14
+ ## [0.11.0]
15
+
16
+ ### Added
17
+
18
+ - Adds `QueueBus.in_context` method. Useful when working with a multithreaded environment to add a description for all events published within this scope.
19
+
20
+ ## [0.10.0]
21
+
22
+ ### Added
23
+ - Ability to unsubscribe from specific queues in an application (`Application#unsubscribe_queue`).
24
+ - `rake queuebus:unsubscribe` can now take two parameters to unsubscribe from specific queues, e.g. `rake queuebus:unsubscribe[my_app_key, my_queue_name]`.
25
+
26
+ ## [0.9.1]
27
+
28
+ ### Added
29
+ - Documented some of the major classes and modules
30
+
31
+ ### Fixed
32
+ - Ran the rubocop autocorrect on the entire codebase.
33
+ - Fixed issue that prevented heartbeat events from firing under certain conditions
34
+
9
35
  ## [0.9.0]
10
36
 
11
37
  ### Added
data/Gemfile CHANGED
@@ -1,5 +1,7 @@
1
- source "http://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'http://rubygems.org'
2
4
 
3
5
  gemspec
4
6
 
5
- gem "rake"
7
+ gem 'rake'
data/README.mdown CHANGED
@@ -141,13 +141,16 @@ event to the appropriate code block.
141
141
  You can also say `QueueBus.local_mode = :suppress` to turn off publishing altogether.
142
142
  This can be helpful inside some sort of migration, for example.
143
143
 
144
- #### Thread Safe Local Modes
144
+ #### Thread Safe Options
145
145
 
146
146
  **!! This is important if you are using workers that utilize multiple threads, such as Sidekiq !!**
147
147
 
148
148
  The above setting is global to the ruby process and modifying it will impact all threads that are
149
149
  currently using QueueBus. If you want to isolate a thread or block of code from QueueBus, you can
150
- use the method `with_local_mode`:
150
+ use the methods `with_local_mode` or `in_context`:
151
+
152
+
153
+ With local mode
151
154
 
152
155
  ```ruby
153
156
  QueueBus.with_local_mode(:suppress) do
@@ -155,7 +158,16 @@ QueueBus.with_local_mode(:suppress) do
155
158
  end
156
159
  ```
157
160
 
158
- The previous value of `local_mode` will be restored after the block exits.
161
+ In context
162
+
163
+ ```ruby
164
+ QueueBus.in_context('some_context') do
165
+ # Context attribute will be set for all events published within this scope.
166
+ end
167
+ ```
168
+
169
+
170
+ The previous values will be restored after the block exits.
159
171
 
160
172
  ### TODO
161
173
 
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH.unshift 'lib'
data/lib/queue-bus.rb CHANGED
@@ -1,8 +1,11 @@
1
- require "queue_bus/version"
2
- require "forwardable"
1
+ # frozen_string_literal: true
3
2
 
4
- module QueueBus
3
+ require 'queue_bus/version'
4
+ require 'forwardable'
5
5
 
6
+ # The main QueueBus module. Most operations you will need to execute should be executed
7
+ # on this top level domain.
8
+ module QueueBus
6
9
  autoload :Application, 'queue_bus/application'
7
10
  autoload :Config, 'queue_bus/config'
8
11
  autoload :Dispatch, 'queue_bus/dispatch'
@@ -22,25 +25,26 @@ module QueueBus
22
25
  autoload :Util, 'queue_bus/util'
23
26
  autoload :Worker, 'queue_bus/worker'
24
27
 
28
+ # A module for all adapters, current and future.
25
29
  module Adapters
26
30
  autoload :Base, 'queue_bus/adapters/base'
27
31
  autoload :Data, 'queue_bus/adapters/data'
28
32
  end
29
33
 
30
34
  class << self
31
-
32
35
  include Publishing
33
36
  extend Forwardable
34
37
 
35
38
  def_delegators :config, :default_app_key=, :default_app_key,
36
- :default_queue=, :default_queue,
37
- :local_mode=, :local_mode, :with_local_mode,
38
- :before_publish=, :before_publish_callback,
39
- :logger=, :logger, :log_application, :log_worker,
40
- :hostname=, :hostname,
41
- :adapter=, :adapter, :has_adapter?,
42
- :incoming_queue=, :incoming_queue,
43
- :redis, :worker_middleware_stack
39
+ :default_queue=, :default_queue,
40
+ :local_mode=, :local_mode, :with_local_mode,
41
+ :before_publish=, :before_publish_callback,
42
+ :logger=, :logger, :log_application, :log_worker,
43
+ :hostname=, :hostname,
44
+ :adapter=, :adapter, :has_adapter?,
45
+ :incoming_queue=, :incoming_queue,
46
+ :redis, :worker_middleware_stack,
47
+ :context=, :context, :in_context
44
48
 
45
49
  def_delegators :_dispatchers, :dispatch, :dispatchers, :dispatcher_by_key, :dispatcher_execute
46
50
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module QueueBus
2
4
  module Adapters
3
5
  class Base
@@ -19,12 +21,12 @@ module QueueBus
19
21
  raise NotImplementedError
20
22
  end
21
23
 
22
- def enqueue(queue_name, klass, json)
24
+ def enqueue(_queue_name, _klass, _json)
23
25
  # enqueue the given class (Driver/Rider) in your queue
24
26
  raise NotImplementedError
25
27
  end
26
28
 
27
- def enqueue_at(epoch_seconds, queue_name, klass, json)
29
+ def enqueue_at(_epoch_seconds, _queue_name, _klass, _json)
28
30
  # enqueue the given class (Publisher) in your queue to run at given time
29
31
  raise NotImplementedError
30
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # a base adapter just for publishing and redis connection
2
4
  module QueueBus
3
5
  module Adapters
@@ -6,17 +8,16 @@ module QueueBus
6
8
  # nothing to do
7
9
  end
8
10
 
9
- def redis=(client)
10
- @redis = client
11
- end
11
+ attr_writer :redis
12
12
 
13
13
  def redis(&block)
14
- raise "no redis instance set" unless @redis
14
+ raise 'no redis instance set' unless @redis
15
+
15
16
  block.call(@redis)
16
17
  end
17
18
 
18
19
  def enqueue(queue_name, klass, json)
19
- push(queue_name, :class => klass.to_s, :args => [json])
20
+ push(queue_name, class: klass.to_s, args: [json])
20
21
  end
21
22
 
22
23
  def enqueue_at(epoch_seconds, queue_name, klass, json)
@@ -24,7 +25,7 @@ module QueueBus
24
25
  delayed_push(epoch_seconds, item)
25
26
  end
26
27
 
27
- def setup_heartbeat!(queue_name)
28
+ def setup_heartbeat!(_queue_name)
28
29
  raise NotImplementedError
29
30
  end
30
31
 
@@ -32,13 +33,13 @@ module QueueBus
32
33
 
33
34
  def push(queue, item)
34
35
  watch_queue(queue)
35
- self.redis { |redis| redis.rpush "queue:#{queue}", ::QueueBus::Util.encode(item) }
36
+ redis { |redis| redis.rpush "queue:#{queue}", ::QueueBus::Util.encode(item) }
36
37
  end
37
38
 
38
39
  # Used internally to keep track of which queues we've created.
39
40
  # Don't call this directly.
40
41
  def watch_queue(queue)
41
- self.redis { |redis| redis.sadd(:queues, queue.to_s) }
42
+ redis { |redis| redis.sadd(:queues, queue.to_s) }
42
43
  end
43
44
 
44
45
  # Used internally to stuff the item into the schedule sorted list.
@@ -46,7 +47,7 @@ module QueueBus
46
47
  # Insertion if O(log(n)).
47
48
  # Returns true if it's the first job to be scheduled at that time, else false
48
49
  def delayed_push(timestamp, item)
49
- self.redis do |redis|
50
+ redis do |redis|
50
51
  # First add this item to the list for this timestamp
51
52
  redis.rpush("delayed:#{timestamp.to_i}", ::QueueBus::Util.encode(item))
52
53
 
@@ -56,9 +57,9 @@ module QueueBus
56
57
  redis.zadd :delayed_queue_schedule, timestamp.to_i, timestamp.to_i
57
58
  end
58
59
  end
59
-
60
+
60
61
  def delayed_job_to_hash_with_queue(queue, klass, args)
61
- {:class => klass.to_s, :args => args, :queue => queue}
62
+ { class: klass.to_s, args: args, queue: queue }
62
63
  end
63
64
  end
64
65
  end
@@ -1,36 +1,53 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module QueueBus
4
+ # An Application is the top level unifier for a number of subscriptions. It allows for
5
+ # the toggling of an entire applications subscriptions.
2
6
  class Application
3
-
4
7
  class << self
5
-
6
8
  def all
7
9
  # note the names arent the same as we started with
8
- ::QueueBus.redis { |redis| redis.smembers(app_list_key).collect{ |val| new(val) } }
10
+ ::QueueBus.redis do |redis|
11
+ app_keys = redis.smembers(app_list_key)
12
+ apps = app_keys.collect { |val| new(val) }
13
+
14
+ hashes = redis.pipelined do
15
+ apps.each do |app|
16
+ redis.hgetall(app.redis_key)
17
+ end
18
+ end
19
+
20
+ apps.zip(hashes).each do |app, hash|
21
+ app._hydrate_redis_hash(hash)
22
+ end
23
+
24
+ apps
25
+ end
9
26
  end
10
27
  end
11
28
 
12
29
  attr_reader :app_key, :redis_key
13
30
 
14
-
15
31
  def initialize(app_key)
16
32
  @app_key = self.class.normalize(app_key)
17
33
  @redis_key = "#{self.class.app_single_key}:#{@app_key}"
18
34
  # raise error if only other chars
19
- raise "Invalid application name" if @app_key.gsub("_", "").size == 0
35
+ raise 'Invalid application name' if @app_key.gsub('_', '').empty?
20
36
  end
21
37
 
22
38
  def subscribe(subscription_list, log = false)
23
39
  @subscriptions = nil
24
40
 
25
- if subscription_list == nil || subscription_list.size == 0
41
+ if subscription_list.nil? || subscription_list.empty?
26
42
  unsubscribe
27
43
  return true
28
44
  end
29
45
 
30
- temp_key = "temp_#{redis_key}:#{rand(999999999)}"
46
+ temp_key = "temp_#{redis_key}:#{rand(999_999_999)}"
31
47
 
32
48
  ::QueueBus.redis do |redis|
33
49
  redis_hash = subscription_list.to_redis
50
+
34
51
  redis_hash.each do |key, hash|
35
52
  redis.hset(temp_key, key, QueueBus::Util.encode(hash))
36
53
  end
@@ -39,16 +56,23 @@ module QueueBus
39
56
  redis.rename(temp_key, redis_key)
40
57
  redis.sadd(self.class.app_list_key, app_key)
41
58
 
42
- if log
43
- redis.hgetall(redis_key).inspect
44
- end
59
+ redis.hgetall(redis_key).inspect if log
45
60
  end
46
61
 
47
62
  true
48
63
  end
49
64
 
65
+ def unsubscribe_queue(queue)
66
+ # Filters out all subscriptions that match the supplied queue name.
67
+ ::QueueBus.redis do |redis|
68
+ read_redis_hash.each do |key, hash_details|
69
+ redis.hdel(redis_key, key) if queue == hash_details["queue_name"]
70
+ end
71
+ end
72
+ end
73
+
50
74
  def unsubscribe
51
- # TODO: clean up known queues?
75
+ # Remove everything.
52
76
  ::QueueBus.redis do |redis|
53
77
  redis.srem(self.class.app_list_key, app_key)
54
78
  redis.del(redis_key)
@@ -68,7 +92,7 @@ module QueueBus
68
92
  def subscription_matches(attributes)
69
93
  out = subscriptions.matches(attributes)
70
94
  out.each do |sub|
71
- sub.app_key = self.app_key
95
+ sub.app_key = app_key
72
96
  end
73
97
  out
74
98
  end
@@ -81,18 +105,22 @@ module QueueBus
81
105
  out
82
106
  end
83
107
 
108
+ def _hydrate_redis_hash(hash)
109
+ @raw_redis_hash = hash
110
+ end
111
+
84
112
  protected
85
113
 
86
114
  def self.normalize(val)
87
- val.to_s.gsub(/\W/, "_").downcase
115
+ val.to_s.gsub(/\W/, '_').downcase
88
116
  end
89
117
 
90
118
  def self.app_list_key
91
- "bus_apps"
119
+ 'bus_apps'
92
120
  end
93
121
 
94
122
  def self.app_single_key
95
- "bus_app"
123
+ 'bus_app'
96
124
  end
97
125
 
98
126
  def event_queues
@@ -105,17 +133,24 @@ module QueueBus
105
133
 
106
134
  def read_redis_hash
107
135
  out = {}
108
- ::QueueBus.redis do |redis|
109
- redis.hgetall(redis_key).each do |key, val|
110
- begin
111
- out[key] = ::QueueBus::Util.decode(val)
112
- rescue ::QueueBus::Util::DecodeException
113
- out[key] = val
114
- end
136
+ raw_redis_hash.each do |key, val|
137
+ begin
138
+ out[key] = ::QueueBus::Util.decode(val)
139
+ rescue ::QueueBus::Util::DecodeException
140
+ out[key] = val
115
141
  end
116
142
  end
117
143
  out
118
144
  end
119
145
 
146
+ private
147
+
148
+ def raw_redis_hash
149
+ return @raw_redis_hash if @raw_redis_hash
150
+
151
+ ::QueueBus.redis do |redis|
152
+ redis.hgetall(redis_key)
153
+ end
154
+ end
120
155
  end
121
156
  end
@@ -9,7 +9,7 @@ module QueueBus
9
9
  attr_accessor :default_queue, :hostname, :incoming_queue, :logger
10
10
 
11
11
  attr_reader :worker_middleware_stack
12
- attr_writer :local_mode
12
+ attr_writer :local_mode, :context
13
13
 
14
14
  def initialize
15
15
  @worker_middleware_stack = QueueBus::Middleware::Stack.new
@@ -24,6 +24,7 @@ module QueueBus
24
24
  Wrap = Struct.new(:value)
25
25
 
26
26
  LOCAL_MODE_VAR = :queue_bus_local_mode
27
+ CONTEXT_VAR = :queue_bus_context
27
28
 
28
29
  # Returns the current local mode of QueueBus
29
30
  def local_mode
@@ -34,6 +35,15 @@ module QueueBus
34
35
  end
35
36
  end
36
37
 
38
+ # Returns the current context of QueueBus
39
+ def context
40
+ if Thread.current.thread_variable_get(CONTEXT_VAR).is_a?(Wrap)
41
+ Thread.current.thread_variable_get(CONTEXT_VAR).value
42
+ else
43
+ @context
44
+ end
45
+ end
46
+
37
47
  # Overrides the current local mode for the duration of a block. This is a threadsafe
38
48
  # implementation. After, the global setting will be resumed.
39
49
  #
@@ -46,6 +56,17 @@ module QueueBus
46
56
  Thread.current.thread_variable_set(LOCAL_MODE_VAR, previous)
47
57
  end
48
58
 
59
+ # Overrides the current bus context (if any) for the duration of a block, adding a
60
+ # `bus_context` attribute set to this value for all events published in this scope.
61
+ # This is a threadsafe implementation. After, the global setting will be resumed.
62
+ def in_context(context)
63
+ previous = Thread.current.thread_variable_get(CONTEXT_VAR)
64
+ Thread.current.thread_variable_set(CONTEXT_VAR, Wrap.new(context))
65
+ yield if block_given?
66
+ ensure
67
+ Thread.current.thread_variable_set(CONTEXT_VAR, previous)
68
+ end
69
+
49
70
  def adapter=(val)
50
71
  raise "Adapter already set to #{@adapter_instance.class.name}" if has_adapter?
51
72