queue-bus 0.8.0 → 0.11.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 +31 -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 +24 -16
  11. data/lib/queue_bus/config.rb +23 -2
  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 +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 +36 -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 +12 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86f211839cfb0c4e6e037abcfd1d73a91bbbe38ae8a08138f2bbad0723e092c7
4
- data.tar.gz: d0474b7d0763983dd5bd54759023e0fe017ced93aa8ecee0b3b599b34ab4e5fd
3
+ metadata.gz: 139511a2de01b9ffb7e58b63ecf62cbc01ae94fe38896282de2ae0dbb13178de
4
+ data.tar.gz: 0e481639105b4dceff9991ee448f378923fdb3ae5820294f954602e96a177243
5
5
  SHA512:
6
- metadata.gz: 4c467c372844f04cdb4fd6d8d827fcbc56427936181f4bd08571da0f82c12014e0c9d6db282df17625e1102b7c0cf604af606478affeee51d1b769583a824fe2
7
- data.tar.gz: 8d16570fa8441b83288790a052d0e09511039b72d6f44a0f9c9c4578d5668a8af04e49836c580f1a9e750602a11a1b607d8db5844e879249a41378a4c2a512f7
6
+ metadata.gz: 3ce3ebbeee0e6be7513af1ed093f5612877a6293a78bad68da502817c10f205ddf69bbc3071ef497f48baef2e66f2823311ef80e579805dbeedccec9b0794bef
7
+ data.tar.gz: 8b93e84ff4b5d6801d6b490560af46532c729eb7ff2b32b0d88e565bd620388d029f1a010027b9032e56731aef936945922bf7afc642453aeb9db9cbeacd0bcd
@@ -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,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.11.0]
10
+
11
+ ### Added
12
+
13
+ - Adds `QueueBus.in_context` method. Useful when working with a multithreaded environment to add a description for all events published within this scope.
14
+
15
+ ## [0.10.0]
16
+
17
+ ### Added
18
+ - Ability to unsubscribe from specific queues in an application (`Application#unsubscribe_queue`).
19
+ - `rake queuebus:unsubscribe` can now take two parameters to unsubscribe from specific queues, e.g. `rake queuebus:unsubscribe[my_app_key, my_queue_name]`.
20
+
21
+ ## [0.9.1]
22
+
23
+ ### Added
24
+ - Documented some of the major classes and modules
25
+
26
+ ### Fixed
27
+ - Ran the rubocop autocorrect on the entire codebase.
28
+ - Fixed issue that prevented heartbeat events from firing under certain conditions
29
+
30
+ ## [0.9.0]
31
+
32
+ ### Added
33
+ - Adds rake tasks to list scheduled jobs as csv
34
+
35
+ ## [0.8.1]
36
+
37
+ ### Fixed
38
+ - `with_local_mode` breaks subsequent calls to `local_mode` on versions less than 2.6.
39
+
9
40
  ## [0.8.0]
10
41
 
11
42
  ### 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,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
@@ -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,16 +24,26 @@ 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
30
- if Thread.current.thread_variable?(LOCAL_MODE_VAR)
31
+ if Thread.current.thread_variable_get(LOCAL_MODE_VAR).is_a?(Wrap)
31
32
  Thread.current.thread_variable_get(LOCAL_MODE_VAR).value
32
33
  else
33
34
  @local_mode
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
 
@@ -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)