shoryuken 6.2.1 → 7.0.0.alpha2

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/base.Dockerfile +1 -1
  3. data/.github/workflows/push.yml +36 -0
  4. data/.github/workflows/specs.yml +40 -30
  5. data/.github/workflows/verify-action-pins.yml +16 -0
  6. data/.gitignore +2 -1
  7. data/.rspec +2 -1
  8. data/.rubocop.yml +6 -1
  9. data/.ruby-version +1 -0
  10. data/Appraisals +8 -27
  11. data/CHANGELOG.md +213 -139
  12. data/Gemfile +2 -7
  13. data/README.md +14 -26
  14. data/Rakefile +2 -0
  15. data/bin/cli/base.rb +1 -2
  16. data/bin/cli/sqs.rb +13 -5
  17. data/bin/shoryuken +2 -1
  18. data/docker-compose.yml +22 -0
  19. data/gemfiles/rails_7_0.gemfile +10 -13
  20. data/gemfiles/rails_7_1.gemfile +19 -0
  21. data/gemfiles/rails_7_2.gemfile +19 -0
  22. data/gemfiles/rails_8_0.gemfile +19 -0
  23. data/lib/shoryuken/body_parser.rb +3 -1
  24. data/lib/shoryuken/client.rb +2 -0
  25. data/lib/shoryuken/default_exception_handler.rb +2 -0
  26. data/lib/shoryuken/default_worker_registry.rb +11 -11
  27. data/lib/shoryuken/environment_loader.rb +6 -6
  28. data/lib/shoryuken/extensions/active_job_adapter.rb +13 -6
  29. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +5 -5
  30. data/lib/shoryuken/extensions/active_job_extensions.rb +2 -0
  31. data/lib/shoryuken/fetcher.rb +4 -2
  32. data/lib/shoryuken/helpers/atomic_boolean.rb +44 -0
  33. data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
  34. data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
  35. data/lib/shoryuken/helpers/hash_utils.rb +56 -0
  36. data/lib/shoryuken/helpers/string_utils.rb +65 -0
  37. data/lib/shoryuken/inline_message.rb +22 -0
  38. data/lib/shoryuken/launcher.rb +2 -0
  39. data/lib/shoryuken/logging.rb +19 -5
  40. data/lib/shoryuken/manager.rb +15 -5
  41. data/lib/shoryuken/message.rb +2 -0
  42. data/lib/shoryuken/middleware/chain.rb +2 -0
  43. data/lib/shoryuken/middleware/server/active_record.rb +2 -0
  44. data/lib/shoryuken/middleware/server/auto_delete.rb +2 -0
  45. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +10 -10
  46. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +5 -3
  47. data/lib/shoryuken/middleware/server/timing.rb +2 -0
  48. data/lib/shoryuken/options.rb +14 -5
  49. data/lib/shoryuken/polling/base_strategy.rb +126 -0
  50. data/lib/shoryuken/polling/queue_configuration.rb +103 -0
  51. data/lib/shoryuken/polling/strict_priority.rb +2 -0
  52. data/lib/shoryuken/polling/weighted_round_robin.rb +2 -0
  53. data/lib/shoryuken/processor.rb +5 -2
  54. data/lib/shoryuken/queue.rb +6 -4
  55. data/lib/shoryuken/runner.rb +9 -12
  56. data/lib/shoryuken/util.rb +6 -6
  57. data/lib/shoryuken/version.rb +3 -1
  58. data/lib/shoryuken/worker/default_executor.rb +2 -0
  59. data/lib/shoryuken/worker/inline_executor.rb +9 -2
  60. data/lib/shoryuken/worker.rb +2 -0
  61. data/lib/shoryuken/worker_registry.rb +2 -0
  62. data/lib/shoryuken.rb +11 -34
  63. data/renovate.json +16 -0
  64. data/shoryuken.gemspec +7 -4
  65. data/spec/integration/launcher_spec.rb +3 -4
  66. data/spec/shared_examples_for_active_job.rb +13 -8
  67. data/spec/shoryuken/body_parser_spec.rb +2 -4
  68. data/spec/shoryuken/client_spec.rb +1 -1
  69. data/spec/shoryuken/default_exception_handler_spec.rb +9 -10
  70. data/spec/shoryuken/default_worker_registry_spec.rb +1 -2
  71. data/spec/shoryuken/environment_loader_spec.rb +9 -8
  72. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +2 -1
  73. data/spec/shoryuken/extensions/active_job_base_spec.rb +2 -1
  74. data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +2 -1
  75. data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +2 -1
  76. data/spec/shoryuken/fetcher_spec.rb +23 -26
  77. data/spec/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
  78. data/spec/shoryuken/helpers/atomic_counter_spec.rb +177 -0
  79. data/spec/shoryuken/helpers/atomic_hash_spec.rb +307 -0
  80. data/spec/shoryuken/helpers/hash_utils_spec.rb +145 -0
  81. data/spec/shoryuken/helpers/string_utils_spec.rb +124 -0
  82. data/spec/shoryuken/helpers_integration_spec.rb +96 -0
  83. data/spec/shoryuken/inline_message_spec.rb +196 -0
  84. data/spec/shoryuken/launcher_spec.rb +1 -2
  85. data/spec/shoryuken/manager_spec.rb +1 -2
  86. data/spec/shoryuken/middleware/chain_spec.rb +1 -1
  87. data/spec/shoryuken/middleware/server/auto_delete_spec.rb +1 -1
  88. data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +1 -1
  89. data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +1 -1
  90. data/spec/shoryuken/middleware/server/timing_spec.rb +1 -1
  91. data/spec/shoryuken/options_spec.rb +4 -4
  92. data/spec/shoryuken/polling/base_strategy_spec.rb +280 -0
  93. data/spec/shoryuken/polling/queue_configuration_spec.rb +195 -0
  94. data/spec/shoryuken/polling/strict_priority_spec.rb +1 -1
  95. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +1 -1
  96. data/spec/shoryuken/processor_spec.rb +1 -1
  97. data/spec/shoryuken/queue_spec.rb +2 -3
  98. data/spec/shoryuken/runner_spec.rb +1 -3
  99. data/spec/shoryuken/util_spec.rb +1 -1
  100. data/spec/shoryuken/worker/default_executor_spec.rb +1 -1
  101. data/spec/shoryuken/worker/inline_executor_spec.rb +57 -1
  102. data/spec/shoryuken/worker_spec.rb +15 -11
  103. data/spec/shoryuken_spec.rb +1 -1
  104. data/spec/spec_helper.rb +19 -4
  105. metadata +79 -35
  106. data/.codeclimate.yml +0 -20
  107. data/.github/FUNDING.yml +0 -12
  108. data/.github/dependabot.yml +0 -6
  109. data/.github/workflows/stale.yml +0 -20
  110. data/.reek.yml +0 -5
  111. data/gemfiles/aws_sdk_core_2.gemfile +0 -21
  112. data/gemfiles/rails_4_2.gemfile +0 -20
  113. data/gemfiles/rails_5_2.gemfile +0 -21
  114. data/gemfiles/rails_6_0.gemfile +0 -21
  115. data/gemfiles/rails_6_1.gemfile +0 -21
  116. data/lib/shoryuken/core_ext.rb +0 -69
  117. data/lib/shoryuken/polling/base.rb +0 -67
  118. data/shoryuken.jpg +0 -0
  119. data/spec/shoryuken/core_ext_spec.rb +0 -40
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Helpers
5
+ # A thread-safe counter implementation using Ruby's Mutex.
6
+ #
7
+ # This class provides atomic operations for incrementing, decrementing, and reading
8
+ # integer values in a thread-safe manner. It serves as a drop-in replacement for
9
+ # Concurrent::AtomicFixnum without requiring external dependencies.
10
+ #
11
+ # The implementation uses a Mutex to ensure thread safety across all Ruby
12
+ # implementations including JRuby, where true parallelism makes atomic operations
13
+ # critical for data integrity.
14
+ #
15
+ # @note This class is optimized for scenarios with frequent atomic updates
16
+ # and occasional reads, such as tracking active worker counts.
17
+ #
18
+ # @example Basic usage
19
+ # counter = Shoryuken::Helpers::AtomicCounter.new(0)
20
+ # counter.increment # => 1
21
+ # counter.increment # => 2
22
+ # counter.value # => 2
23
+ # counter.decrement # => 1
24
+ #
25
+ # @example Tracking busy processors
26
+ # @busy_processors = Shoryuken::Helpers::AtomicCounter.new(0)
27
+ #
28
+ # # When starting work
29
+ # @busy_processors.increment
30
+ #
31
+ # # When work is done
32
+ # @busy_processors.decrement
33
+ #
34
+ # # Check current load
35
+ # current_busy = @busy_processors.value
36
+ class AtomicCounter
37
+ # Creates a new atomic counter with the specified initial value.
38
+ #
39
+ # @param initial_value [Integer] The starting value for the counter
40
+ # @return [AtomicCounter] A new atomic counter instance
41
+ #
42
+ # @example Create counter starting at zero
43
+ # counter = Shoryuken::Helpers::AtomicCounter.new
44
+ # counter.value # => 0
45
+ #
46
+ # @example Create counter with custom initial value
47
+ # counter = Shoryuken::Helpers::AtomicCounter.new(100)
48
+ # counter.value # => 100
49
+ def initialize(initial_value = 0)
50
+ @mutex = Mutex.new
51
+ @value = initial_value
52
+ end
53
+
54
+ # Returns the current value of the counter.
55
+ #
56
+ # This operation is thread-safe and will return a consistent value
57
+ # even when called concurrently with increment/decrement operations.
58
+ #
59
+ # @return [Integer] The current counter value
60
+ #
61
+ # @example Reading the current value
62
+ # counter = Shoryuken::Helpers::AtomicCounter.new(42)
63
+ # counter.value # => 42
64
+ def value
65
+ @mutex.synchronize { @value }
66
+ end
67
+
68
+ # Atomically increments the counter by 1 and returns the new value.
69
+ #
70
+ # This operation is thread-safe and can be called concurrently from
71
+ # multiple threads without risk of data corruption or lost updates.
72
+ #
73
+ # @return [Integer] The new counter value after incrementing
74
+ #
75
+ # @example Incrementing the counter
76
+ # counter = Shoryuken::Helpers::AtomicCounter.new(5)
77
+ # counter.increment # => 6
78
+ # counter.increment # => 7
79
+ def increment
80
+ @mutex.synchronize { @value += 1 }
81
+ end
82
+
83
+ # Atomically decrements the counter by 1 and returns the new value.
84
+ #
85
+ # This operation is thread-safe and can be called concurrently from
86
+ # multiple threads without risk of data corruption or lost updates.
87
+ # The counter can go negative if decremented below zero.
88
+ #
89
+ # @return [Integer] The new counter value after decrementing
90
+ #
91
+ # @example Decrementing the counter
92
+ # counter = Shoryuken::Helpers::AtomicCounter.new(5)
93
+ # counter.decrement # => 4
94
+ # counter.decrement # => 3
95
+ #
96
+ # @example Counter can go negative
97
+ # counter = Shoryuken::Helpers::AtomicCounter.new(0)
98
+ # counter.decrement # => -1
99
+ def decrement
100
+ @mutex.synchronize { @value -= 1 }
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Helpers
5
+ # A thread-safe hash implementation using Ruby's Mutex for all operations.
6
+ #
7
+ # This class provides a hash-like interface with thread-safe operations, serving as a
8
+ # drop-in replacement for Concurrent::Hash without requiring external dependencies.
9
+ # The implementation uses a single mutex to protect both read and write operations,
10
+ # ensuring complete thread safety across all Ruby implementations including JRuby.
11
+ #
12
+ # Since hash operations (lookup, assignment) are very fast, the mutex overhead is
13
+ # minimal while providing guaranteed safety and simplicity. This approach avoids
14
+ # the complexity of copy-on-write while maintaining excellent performance for
15
+ # typical usage patterns.
16
+ #
17
+ # @note This implementation uses mutex synchronization for all operations,
18
+ # ensuring complete thread safety with minimal performance impact.
19
+ #
20
+ # @note All operations are atomic and will never see partial effects from
21
+ # concurrent operations.
22
+ #
23
+ # @example Basic hash operations
24
+ # hash = Shoryuken::Helpers::AtomicHash.new
25
+ # hash['key'] = 'value'
26
+ # hash['key'] # => 'value'
27
+ # hash.keys # => ['key']
28
+ # hash.clear
29
+ # hash['key'] # => nil
30
+ #
31
+ # @example Worker registry usage
32
+ # @workers = Shoryuken::Helpers::AtomicHash.new
33
+ #
34
+ # # Registration (infrequent writes)
35
+ # @workers['queue_name'] = WorkerClass
36
+ #
37
+ # # Lookups (frequent reads)
38
+ # worker_class = @workers['queue_name']
39
+ # available_queues = @workers.keys
40
+ # worker_class = @workers.fetch('queue_name', DefaultWorker)
41
+ #
42
+ # @example Thread-safe concurrent access
43
+ # hash = Shoryuken::Helpers::AtomicHash.new
44
+ #
45
+ # # Multiple threads can safely write
46
+ # Thread.new { hash['key1'] = 'value1' }
47
+ # Thread.new { hash['key2'] = 'value2' }
48
+ #
49
+ # # Multiple threads can safely read concurrently
50
+ # Thread.new { puts hash['key1'] }
51
+ # Thread.new { puts hash.keys.size }
52
+ class AtomicHash
53
+ # Creates a new empty atomic hash.
54
+ #
55
+ # The hash starts empty and ready to accept key-value pairs through
56
+ # thread-safe operations.
57
+ #
58
+ # @return [AtomicHash] A new empty atomic hash instance
59
+ #
60
+ # @example Creating an empty hash
61
+ # hash = Shoryuken::Helpers::AtomicHash.new
62
+ # hash.keys # => []
63
+ def initialize
64
+ @mutex = Mutex.new
65
+ @hash = {}
66
+ end
67
+
68
+ # Returns the value associated with the given key.
69
+ #
70
+ # This operation is thread-safe and will return a consistent value
71
+ # even when called concurrently with write operations.
72
+ #
73
+ # @param key [Object] The key to look up
74
+ # @return [Object, nil] The value associated with the key, or nil if not found
75
+ #
76
+ # @example Reading values
77
+ # hash = Shoryuken::Helpers::AtomicHash.new
78
+ # hash['existing'] = 'value'
79
+ # hash['existing'] # => 'value'
80
+ # hash['missing'] # => nil
81
+ #
82
+ # @example Works with any key type
83
+ # hash = Shoryuken::Helpers::AtomicHash.new
84
+ # hash[:symbol] = 'symbol_value'
85
+ # hash[42] = 'number_value'
86
+ # hash[:symbol] # => 'symbol_value'
87
+ # hash[42] # => 'number_value'
88
+ def [](key)
89
+ @mutex.synchronize { @hash[key] }
90
+ end
91
+
92
+ # Sets the value for the given key.
93
+ #
94
+ # This is a thread-safe write operation that ensures data integrity
95
+ # when called concurrently with other read or write operations.
96
+ #
97
+ # @param key [Object] The key to set
98
+ # @param value [Object] The value to associate with the key
99
+ # @return [Object] The assigned value
100
+ #
101
+ # @example Setting values
102
+ # hash = Shoryuken::Helpers::AtomicHash.new
103
+ # hash['queue1'] = 'Worker1'
104
+ # hash['queue2'] = 'Worker2'
105
+ # hash['queue1'] # => 'Worker1'
106
+ #
107
+ # @example Overwriting values
108
+ # hash = Shoryuken::Helpers::AtomicHash.new
109
+ # hash['key'] = 'old_value'
110
+ # hash['key'] = 'new_value'
111
+ # hash['key'] # => 'new_value'
112
+ def []=(key, value)
113
+ @mutex.synchronize { @hash[key] = value }
114
+ end
115
+
116
+ # Removes all key-value pairs from the hash.
117
+ #
118
+ # This is a thread-safe write operation that ensures atomicity
119
+ # when called concurrently with other operations.
120
+ #
121
+ # @return [Hash] An empty hash (for compatibility with standard Hash#clear)
122
+ #
123
+ # @example Clearing all entries
124
+ # hash = Shoryuken::Helpers::AtomicHash.new
125
+ # hash['key1'] = 'value1'
126
+ # hash['key2'] = 'value2'
127
+ # hash.keys.size # => 2
128
+ # hash.clear
129
+ # hash.keys.size # => 0
130
+ # hash['key1'] # => nil
131
+ def clear
132
+ @mutex.synchronize { @hash.clear }
133
+ end
134
+
135
+ # Returns an array of all keys in the hash.
136
+ #
137
+ # This operation is thread-safe and will return a consistent snapshot
138
+ # of keys even when called concurrently with write operations.
139
+ #
140
+ # @return [Array] An array containing all keys in the hash
141
+ #
142
+ # @example Getting all keys
143
+ # hash = Shoryuken::Helpers::AtomicHash.new
144
+ # hash['queue1'] = 'Worker1'
145
+ # hash['queue2'] = 'Worker2'
146
+ # hash.keys # => ['queue1', 'queue2'] (order not guaranteed)
147
+ #
148
+ # @example Empty hash returns empty array
149
+ # hash = Shoryuken::Helpers::AtomicHash.new
150
+ # hash.keys # => []
151
+ def keys
152
+ @mutex.synchronize { @hash.keys }
153
+ end
154
+
155
+ # Returns the value for the given key, or a default value if the key is not found.
156
+ #
157
+ # This operation is thread-safe and will return a consistent value
158
+ # even when called concurrently with write operations.
159
+ #
160
+ # @param key [Object] The key to look up
161
+ # @param default [Object] The value to return if the key is not found
162
+ # @return [Object] The value associated with the key, or the default value
163
+ #
164
+ # @example Fetching with defaults
165
+ # hash = Shoryuken::Helpers::AtomicHash.new
166
+ # hash['existing'] = 'found'
167
+ # hash.fetch('existing', 'default') # => 'found'
168
+ # hash.fetch('missing', 'default') # => 'default'
169
+ #
170
+ # @example Default parameter is optional
171
+ # hash = Shoryuken::Helpers::AtomicHash.new
172
+ # hash.fetch('missing') # => nil
173
+ #
174
+ # @example Useful for providing fallback collections
175
+ # hash = Shoryuken::Helpers::AtomicHash.new
176
+ # workers = hash.fetch('queue_name', []) # => [] if not found
177
+ def fetch(key, default = nil)
178
+ @mutex.synchronize { @hash.fetch(key, default) }
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Helpers
5
+ # Utility methods for hash manipulation.
6
+ #
7
+ # This module provides helper methods for common hash operations that were
8
+ # previously implemented as core class extensions. By using a dedicated
9
+ # helper module, we avoid polluting the global namespace while maintaining
10
+ # the same functionality.
11
+ #
12
+ # @example Basic usage
13
+ # hash = { 'key1' => 'value1', 'key2' => { 'nested' => 'value2' } }
14
+ # symbolized = Shoryuken::Helpers::HashUtils.deep_symbolize_keys(hash)
15
+ # # => { key1: 'value1', key2: { nested: 'value2' } }
16
+ module HashUtils
17
+ class << self
18
+ # Recursively converts hash keys to symbols.
19
+ #
20
+ # This method traverses a hash structure and converts all string keys
21
+ # to symbols, including nested hashes. Non-hash values are left unchanged.
22
+ # This is useful for normalizing configuration data loaded from YAML files.
23
+ #
24
+ # @param hash [Hash, Object] The hash to convert, or any other object
25
+ # @return [Hash, Object] Hash with symbolized keys, or the original object if not a hash
26
+ #
27
+ # @example Converting a simple hash
28
+ # hash = { 'key1' => 'value1', 'key2' => 'value2' }
29
+ # HashUtils.deep_symbolize_keys(hash)
30
+ # # => { key1: 'value1', key2: 'value2' }
31
+ #
32
+ # @example Converting a nested hash
33
+ # hash = { 'config' => { 'timeout' => 30, 'retries' => 3 } }
34
+ # HashUtils.deep_symbolize_keys(hash)
35
+ # # => { config: { timeout: 30, retries: 3 } }
36
+ #
37
+ # @example Handling non-hash input gracefully
38
+ # HashUtils.deep_symbolize_keys('not a hash')
39
+ # # => 'not a hash'
40
+ #
41
+ # @example Mixed value types
42
+ # hash = { 'string' => 'value', 'number' => 42, 'nested' => { 'bool' => true } }
43
+ # HashUtils.deep_symbolize_keys(hash)
44
+ # # => { string: 'value', number: 42, nested: { bool: true } }
45
+ def deep_symbolize_keys(hash)
46
+ return hash unless hash.is_a?(Hash)
47
+
48
+ hash.each_with_object({}) do |(key, value), result|
49
+ symbol_key = key.is_a?(String) ? key.to_sym : key
50
+ result[symbol_key] = value.is_a?(Hash) ? deep_symbolize_keys(value) : value
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Helpers
5
+ # Utility methods for string manipulation.
6
+ #
7
+ # This module provides helper methods for common string operations that were
8
+ # previously implemented as core class extensions. By using a dedicated
9
+ # helper module, we avoid polluting the global namespace while maintaining
10
+ # the same functionality.
11
+ #
12
+ # @example Basic usage
13
+ # klass = Shoryuken::Helpers::StringUtils.constantize('MyWorker')
14
+ # # => MyWorker
15
+ module StringUtils
16
+ class << self
17
+ # Converts a string to a constant.
18
+ #
19
+ # This method takes a string representation of a constant name and returns
20
+ # the actual constant. It handles nested constants (e.g., 'Foo::Bar') and
21
+ # leading double colons (e.g., '::Object'). This is commonly used for
22
+ # dynamically loading worker classes from configuration.
23
+ #
24
+ # @param string [String] The string to convert to a constant
25
+ # @return [Class, Module] The constant represented by the string
26
+ # @raise [NameError] if the constant is not found or not defined
27
+ #
28
+ # @example Converting a simple class name
29
+ # StringUtils.constantize('String')
30
+ # # => String
31
+ #
32
+ # @example Converting a nested constant
33
+ # StringUtils.constantize('Shoryuken::Worker')
34
+ # # => Shoryuken::Worker
35
+ #
36
+ # @example Handling leading double colon
37
+ # StringUtils.constantize('::Object')
38
+ # # => Object
39
+ #
40
+ # @example Worker class loading
41
+ # worker_class = StringUtils.constantize('MyApp::EmailWorker')
42
+ # worker_instance = worker_class.new
43
+ #
44
+ # @example Error handling
45
+ # begin
46
+ # StringUtils.constantize('NonExistentClass')
47
+ # rescue NameError => e
48
+ # puts "Class not found: #{e.message}"
49
+ # end
50
+ def constantize(string)
51
+ names = string.split('::')
52
+ names.shift if names.empty? || names.first.empty?
53
+
54
+ constant = Object
55
+
56
+ names.each do |name|
57
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
58
+ end
59
+
60
+ constant
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ # A high-performance alternative to OpenStruct for representing SQS messages.
5
+ #
6
+ # InlineMessage is a Struct-based implementation that provides the same interface
7
+ # as the previous OpenStruct-based message representation but with significantly
8
+ # better performance characteristics. It contains all the essential attributes
9
+ # needed to represent an Amazon SQS message within the Shoryuken framework.
10
+ InlineMessage = Struct.new(
11
+ :body,
12
+ :attributes,
13
+ :md5_of_body,
14
+ :md5_of_message_attributes,
15
+ :message_attributes,
16
+ :message_id,
17
+ :receipt_handle,
18
+ :delete,
19
+ :queue_name,
20
+ keyword_init: true
21
+ )
22
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  class Launcher
3
5
  include Util
@@ -1,12 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
  require 'logger'
3
5
 
4
6
  module Shoryuken
5
7
  module Logging
6
- class Pretty < Logger::Formatter
7
- # Provide a call() method that returns the formatted message.
8
- def call(severity, time, _program_name, message)
9
- "#{time.utc.iso8601} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
8
+ class Base < ::Logger::Formatter
9
+ def tid
10
+ Thread.current['shoryuken_tid'] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
10
11
  end
11
12
 
12
13
  def context
@@ -15,6 +16,19 @@ module Shoryuken
15
16
  end
16
17
  end
17
18
 
19
+ class Pretty < Base
20
+ # Provide a call() method that returns the formatted message.
21
+ def call(severity, time, _program_name, message)
22
+ "#{time.utc.iso8601} #{Process.pid} TID-#{tid}#{context} #{severity}: #{message}\n"
23
+ end
24
+ end
25
+
26
+ class WithoutTimestamp < Base
27
+ def call(severity, _time, _program_name, message)
28
+ "pid=#{Process.pid} tid=#{tid}#{context} #{severity}: #{message}\n"
29
+ end
30
+ end
31
+
18
32
  def self.with_context(msg)
19
33
  Thread.current[:shoryuken_context] = msg
20
34
  yield
@@ -34,7 +48,7 @@ module Shoryuken
34
48
  end
35
49
 
36
50
  def self.logger=(log)
37
- @logger = (log || Logger.new('/dev/null'))
51
+ @logger = log || Logger.new('/dev/null')
38
52
  end
39
53
  end
40
54
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  class Manager
3
5
  include Util
4
6
 
5
7
  BATCH_LIMIT = 10
6
- # See https://github.com/phstc/shoryuken/issues/348#issuecomment-292847028
8
+ # See https://github.com/ruby-shoryuken/shoryuken/issues/348#issuecomment-292847028
7
9
  MIN_DISPATCH_INTERVAL = 0.1
8
10
 
9
11
  attr_reader :group
@@ -13,10 +15,10 @@ module Shoryuken
13
15
  @fetcher = fetcher
14
16
  @polling_strategy = polling_strategy
15
17
  @max_processors = concurrency
16
- @busy_processors = Concurrent::AtomicFixnum.new(0)
18
+ @busy_processors = Shoryuken::Helpers::AtomicCounter.new(0)
17
19
  @executor = executor
18
- @running = Concurrent::AtomicBoolean.new(true)
19
- @stop_new_dispatching = Concurrent::AtomicBoolean.new(false)
20
+ @running = Shoryuken::Helpers::AtomicBoolean.new(true)
21
+ @stop_new_dispatching = Shoryuken::Helpers::AtomicBoolean.new(false)
20
22
  @dispatching_release_signal = ::Queue.new
21
23
  end
22
24
 
@@ -97,7 +99,15 @@ module Shoryuken
97
99
  fire_utilization_update_event
98
100
 
99
101
  Concurrent::Promise
100
- .execute(executor: @executor) { Processor.process(queue_name, sqs_msg) }
102
+ .execute(executor: @executor) do
103
+ original_priority = Thread.current.priority
104
+ begin
105
+ Thread.current.priority = Shoryuken.thread_priority
106
+ Processor.process(queue_name, sqs_msg)
107
+ ensure
108
+ Thread.current.priority = original_priority
109
+ end
110
+ end
101
111
  .then { processor_done(queue_name) }
102
112
  .rescue { processor_done(queue_name) }
103
113
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  class Message
3
5
  extend Forwardable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  # Middleware is code configured to run before/after
3
5
  # a message is processed. It is patterned after Rack
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -29,16 +31,14 @@ module Shoryuken
29
31
  queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
30
32
 
31
33
  Concurrent::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
32
- begin
33
- logger.debug do
34
- "Extending message #{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s"
35
- end
36
-
37
- sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
38
- rescue => ex
39
- logger.error do
40
- "Could not auto extend the message #{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{ex.message}"
41
- end
34
+ logger.debug do
35
+ "Extending message #{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s"
36
+ end
37
+
38
+ sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
39
+ rescue => e
40
+ logger.error do
41
+ "Could not auto extend the message #{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{e.message}"
42
42
  end
43
43
  end
44
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -14,7 +16,7 @@ module Shoryuken
14
16
 
15
17
  started_at = Time.now
16
18
  yield
17
- rescue => ex
19
+ rescue => e
18
20
  retry_intervals = worker.class.get_shoryuken_options['retry_intervals']
19
21
 
20
22
  if retry_intervals.nil? || !handle_failure(sqs_msg, started_at, retry_intervals)
@@ -23,9 +25,9 @@ module Shoryuken
23
25
  raise
24
26
  end
25
27
 
26
- logger.warn { "Message #{sqs_msg.message_id} will attempt retry due to error: #{ex.message}" }
28
+ logger.warn { "Message #{sqs_msg.message_id} will attempt retry due to error: #{e.message}" }
27
29
  # since we didn't raise, lets log the backtrace for debugging purposes.
28
- logger.debug { ex.backtrace.join("\n") } unless ex.backtrace.nil?
30
+ logger.debug { e.backtrace.join("\n") } unless e.backtrace.nil?
29
31
  end
30
32
 
31
33
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  module Middleware
3
5
  module Server
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Shoryuken
2
4
  class Options
3
5
  DEFAULTS = {
6
+ thread_priority: -1,
4
7
  concurrency: 25,
5
8
  queues: [],
6
9
  aws: {},
@@ -16,10 +19,12 @@ module Shoryuken
16
19
  }
17
20
  }.freeze
18
21
 
19
- attr_accessor :active_job_queue_name_prefixing, :cache_visibility_timeout, :groups,
20
- :launcher_executor, :reloader, :enable_reloading,
21
- :start_callback, :stop_callback, :worker_executor, :worker_registry, :exception_handlers
22
- attr_writer :default_worker_options, :sqs_client
22
+ attr_accessor :active_job_queue_name_prefixing, :cache_visibility_timeout,
23
+ :groups, :launcher_executor, :reloader, :enable_reloading,
24
+ :start_callback, :stop_callback, :worker_executor, :worker_registry,
25
+ :exception_handlers
26
+
27
+ attr_writer :default_worker_options, :sqs_client, :logger
23
28
  attr_reader :sqs_client_receive_message_opts
24
29
 
25
30
  def initialize
@@ -95,7 +100,11 @@ module Shoryuken
95
100
  end
96
101
 
97
102
  def logger
98
- Shoryuken::Logging.logger
103
+ @logger ||= Shoryuken::Logging.logger
104
+ end
105
+
106
+ def thread_priority
107
+ @thread_priority ||= options[:thread_priority]
99
108
  end
100
109
 
101
110
  def register_worker(*args)