queue-bus 0.7.0 → 0.10.0

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -0
  3. data/CHANGELOG.md +30 -0
  4. data/Gemfile +4 -2
  5. data/README.mdown +16 -0
  6. data/Rakefile +2 -0
  7. data/lib/queue-bus.rb +15 -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 +24 -16
  11. data/lib/queue_bus/config.rb +31 -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 +31 -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 +35 -11
  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 +46 -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 +8 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89735322de0ab91d94ca27b4f58598ef5049f829adfdc4788aa84eccd4d4597e
4
- data.tar.gz: b6955c5ac09371018feddf9236f756de49c024ae1ac199b1498c00137761ab6d
3
+ metadata.gz: 315c7263eb40e8d6165b96b8f50738ff5861127f35f1ce282f7b1d8a20914290
4
+ data.tar.gz: 067034c67ce6232de23fe552c1fc3bac50ca64dd898dec9b4958009ce6067fe9
5
5
  SHA512:
6
- metadata.gz: f4c49c5180981e584fa91e0ba8a185c82b82f9c82533b36a75e2dc57cab75ff327d3585ac109940f234c9414c64ebae33aaa1f54ccbf5ab4b0e16cd1965128bd
7
- data.tar.gz: 7486eb2fbd2cb8ede93beb3af7b2046b224e74983986045577997f227d9df4e056aac8f8054bb42f09c1194354b4c0306c45a30c059c6cab099710365ee58a9e
6
+ metadata.gz: dfea248252d2b17eca07eb5dae5e7feb4e32ac8673f154f959f0a622221dd466470f0b243f2e8c07191c951897b646c8341e5df3933556361dc1fd831de483a6
7
+ data.tar.gz: 6585cc65f3bb494161fc18e45d421203278db133b3471747caa8d526da7a551b524a6ba6289320f4a6f7fe80566f6bb7436dadda267d69d96497c7e2f14e64a2
@@ -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
@@ -6,6 +6,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.0]
10
+
11
+ ### Added
12
+ - Ability to unsubscribe from specific queues in an application (`Application#unsubscribe_queue`).
13
+ - `rake queuebus:unsubscribe` can now take two parameters to unsubscribe from specific queues, e.g. `rake queuebus:unsubscribe[my_app_key, my_queue_name]`.
14
+
15
+ ## [0.9.1]
16
+
17
+ ### Added
18
+ - Documented some of the major classes and modules
19
+
20
+ ### Fixed
21
+ - Ran the rubocop autocorrect on the entire codebase.
22
+ - Fixed issue that prevented heartbeat events from firing under certain conditions
23
+
24
+ ## [0.9.0]
25
+
26
+ ### Added
27
+ - Adds rake tasks to list scheduled jobs as csv
28
+
29
+ ## [0.8.1]
30
+
31
+ ### Fixed
32
+ - `with_local_mode` breaks subsequent calls to `local_mode` on versions less than 2.6.
33
+
34
+ ## [0.8.0]
35
+
36
+ ### Added
37
+ - Adds `QueueBus.with_local_mode` method. Useful when working with a multithreaded environment.
38
+
9
39
  ## [0.7.0]
10
40
 
11
41
  ### 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'
@@ -141,6 +141,22 @@ 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
145
+
146
+ **!! This is important if you are using workers that utilize multiple threads, such as Sidekiq !!**
147
+
148
+ The above setting is global to the ruby process and modifying it will impact all threads that are
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`:
151
+
152
+ ```ruby
153
+ QueueBus.with_local_mode(:suppress) do
154
+ # QueueBus will be suppressed on this thread, within this block.
155
+ end
156
+ ```
157
+
158
+ The previous value of `local_mode` will be restored after the block exits.
159
+
144
160
  ### TODO
145
161
 
146
162
  * Replace local modes with adapters
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH.unshift 'lib'
@@ -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,25 @@ 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,
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
44
47
 
45
48
  def_delegators :_dispatchers, :dispatch, :dispatchers, :dispatcher_by_key, :dispatcher_execute
46
49
 
@@ -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,38 @@
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 { |redis| redis.smembers(app_list_key).collect { |val| new(val) } }
9
11
  end
10
12
  end
11
13
 
12
14
  attr_reader :app_key, :redis_key
13
15
 
14
-
15
16
  def initialize(app_key)
16
17
  @app_key = self.class.normalize(app_key)
17
18
  @redis_key = "#{self.class.app_single_key}:#{@app_key}"
18
19
  # raise error if only other chars
19
- raise "Invalid application name" if @app_key.gsub("_", "").size == 0
20
+ raise 'Invalid application name' if @app_key.gsub('_', '').empty?
20
21
  end
21
22
 
22
23
  def subscribe(subscription_list, log = false)
23
24
  @subscriptions = nil
24
25
 
25
- if subscription_list == nil || subscription_list.size == 0
26
+ if subscription_list.nil? || subscription_list.empty?
26
27
  unsubscribe
27
28
  return true
28
29
  end
29
30
 
30
- temp_key = "temp_#{redis_key}:#{rand(999999999)}"
31
+ temp_key = "temp_#{redis_key}:#{rand(999_999_999)}"
31
32
 
32
33
  ::QueueBus.redis do |redis|
33
34
  redis_hash = subscription_list.to_redis
35
+
34
36
  redis_hash.each do |key, hash|
35
37
  redis.hset(temp_key, key, QueueBus::Util.encode(hash))
36
38
  end
@@ -39,16 +41,23 @@ module QueueBus
39
41
  redis.rename(temp_key, redis_key)
40
42
  redis.sadd(self.class.app_list_key, app_key)
41
43
 
42
- if log
43
- redis.hgetall(redis_key).inspect
44
- end
44
+ redis.hgetall(redis_key).inspect if log
45
45
  end
46
46
 
47
47
  true
48
48
  end
49
49
 
50
+ def unsubscribe_queue(queue)
51
+ # Filters out all subscriptions that match the supplied queue name.
52
+ ::QueueBus.redis do |redis|
53
+ read_redis_hash.each do |key, hash_details|
54
+ redis.hdel(redis_key, key) if queue == hash_details["queue_name"]
55
+ end
56
+ end
57
+ end
58
+
50
59
  def unsubscribe
51
- # TODO: clean up known queues?
60
+ # Remove everything.
52
61
  ::QueueBus.redis do |redis|
53
62
  redis.srem(self.class.app_list_key, app_key)
54
63
  redis.del(redis_key)
@@ -68,7 +77,7 @@ module QueueBus
68
77
  def subscription_matches(attributes)
69
78
  out = subscriptions.matches(attributes)
70
79
  out.each do |sub|
71
- sub.app_key = self.app_key
80
+ sub.app_key = app_key
72
81
  end
73
82
  out
74
83
  end
@@ -84,15 +93,15 @@ module QueueBus
84
93
  protected
85
94
 
86
95
  def self.normalize(val)
87
- val.to_s.gsub(/\W/, "_").downcase
96
+ val.to_s.gsub(/\W/, '_').downcase
88
97
  end
89
98
 
90
99
  def self.app_list_key
91
- "bus_apps"
100
+ 'bus_apps'
92
101
  end
93
102
 
94
103
  def self.app_single_key
95
- "bus_app"
104
+ 'bus_app'
96
105
  end
97
106
 
98
107
  def event_queues
@@ -116,6 +125,5 @@ module QueueBus
116
125
  end
117
126
  out
118
127
  end
119
-
120
128
  end
121
129
  end
@@ -6,9 +6,10 @@ require 'logger'
6
6
  module QueueBus
7
7
  # This class contains all the configuration for a running queue bus application.
8
8
  class Config
9
- attr_accessor :default_queue, :local_mode, :hostname, :incoming_queue, :logger
9
+ attr_accessor :default_queue, :hostname, :incoming_queue, :logger
10
10
 
11
11
  attr_reader :worker_middleware_stack
12
+ attr_writer :local_mode
12
13
 
13
14
  def initialize
14
15
  @worker_middleware_stack = QueueBus::Middleware::Stack.new
@@ -16,6 +17,35 @@ module QueueBus
16
17
  @hostname = Socket.gethostname
17
18
  end
18
19
 
20
+ # A wrapper that is always "truthy" but can contain an inner value. This is useful for
21
+ # checking that a thread local variable is set to a value, even if that value happens to
22
+ # be nil. This is important because setting a thread local value to nil will cause it to
23
+ # be deleted.
24
+ Wrap = Struct.new(:value)
25
+
26
+ LOCAL_MODE_VAR = :queue_bus_local_mode
27
+
28
+ # Returns the current local mode of QueueBus
29
+ def local_mode
30
+ if Thread.current.thread_variable_get(LOCAL_MODE_VAR).is_a?(Wrap)
31
+ Thread.current.thread_variable_get(LOCAL_MODE_VAR).value
32
+ else
33
+ @local_mode
34
+ end
35
+ end
36
+
37
+ # Overrides the current local mode for the duration of a block. This is a threadsafe
38
+ # implementation. After, the global setting will be resumed.
39
+ #
40
+ # @param mode [Symbol] the mode to switch to
41
+ def with_local_mode(mode)
42
+ previous = Thread.current.thread_variable_get(LOCAL_MODE_VAR)
43
+ Thread.current.thread_variable_set(LOCAL_MODE_VAR, Wrap.new(mode))
44
+ yield if block_given?
45
+ ensure
46
+ Thread.current.thread_variable_set(LOCAL_MODE_VAR, previous)
47
+ end
48
+
19
49
  def adapter=(val)
20
50
  raise "Adapter already set to #{@adapter_instance.class.name}" if has_adapter?
21
51
 
@@ -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("default", key, matcher_hash, block)
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 = self.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 ||= { "bus_event_type" => key }
52
- add_subscription("#{app_key}_#{queue}", key, "::QueueBus::Rider", matcher_hash, block)
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)