shoryuken 7.0.0.alpha1 → 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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +3 -3
  3. data/.github/workflows/specs.yml +6 -9
  4. data/.github/workflows/verify-action-pins.yml +1 -1
  5. data/.rspec +2 -1
  6. data/.ruby-version +1 -1
  7. data/Appraisals +0 -6
  8. data/CHANGELOG.md +186 -142
  9. data/Gemfile +1 -0
  10. data/README.md +12 -13
  11. data/bin/cli/base.rb +1 -2
  12. data/bin/cli/sqs.rb +5 -4
  13. data/bin/shoryuken +2 -1
  14. data/gemfiles/rails_7_0.gemfile +10 -10
  15. data/gemfiles/rails_7_1.gemfile +10 -9
  16. data/gemfiles/rails_7_2.gemfile +10 -9
  17. data/gemfiles/rails_8_0.gemfile +10 -9
  18. data/lib/shoryuken/body_parser.rb +3 -1
  19. data/lib/shoryuken/client.rb +2 -0
  20. data/lib/shoryuken/default_exception_handler.rb +2 -0
  21. data/lib/shoryuken/default_worker_registry.rb +11 -11
  22. data/lib/shoryuken/environment_loader.rb +6 -6
  23. data/lib/shoryuken/extensions/active_job_adapter.rb +8 -6
  24. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +5 -5
  25. data/lib/shoryuken/extensions/active_job_extensions.rb +2 -0
  26. data/lib/shoryuken/fetcher.rb +4 -2
  27. data/lib/shoryuken/helpers/atomic_boolean.rb +44 -0
  28. data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
  29. data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
  30. data/lib/shoryuken/helpers/hash_utils.rb +56 -0
  31. data/lib/shoryuken/helpers/string_utils.rb +65 -0
  32. data/lib/shoryuken/inline_message.rb +22 -0
  33. data/lib/shoryuken/launcher.rb +2 -0
  34. data/lib/shoryuken/logging.rb +19 -5
  35. data/lib/shoryuken/manager.rb +6 -4
  36. data/lib/shoryuken/message.rb +2 -0
  37. data/lib/shoryuken/middleware/chain.rb +2 -0
  38. data/lib/shoryuken/middleware/server/active_record.rb +2 -0
  39. data/lib/shoryuken/middleware/server/auto_delete.rb +2 -0
  40. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +10 -10
  41. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +5 -3
  42. data/lib/shoryuken/middleware/server/timing.rb +2 -0
  43. data/lib/shoryuken/options.rb +9 -5
  44. data/lib/shoryuken/polling/base_strategy.rb +126 -0
  45. data/lib/shoryuken/polling/queue_configuration.rb +103 -0
  46. data/lib/shoryuken/polling/strict_priority.rb +2 -0
  47. data/lib/shoryuken/polling/weighted_round_robin.rb +2 -0
  48. data/lib/shoryuken/processor.rb +5 -2
  49. data/lib/shoryuken/queue.rb +6 -4
  50. data/lib/shoryuken/runner.rb +9 -12
  51. data/lib/shoryuken/util.rb +6 -6
  52. data/lib/shoryuken/version.rb +3 -1
  53. data/lib/shoryuken/worker/default_executor.rb +2 -0
  54. data/lib/shoryuken/worker/inline_executor.rb +3 -1
  55. data/lib/shoryuken/worker.rb +2 -0
  56. data/lib/shoryuken/worker_registry.rb +2 -0
  57. data/lib/shoryuken.rb +8 -28
  58. data/shoryuken.gemspec +6 -6
  59. data/spec/integration/launcher_spec.rb +2 -3
  60. data/spec/shared_examples_for_active_job.rb +13 -8
  61. data/spec/shoryuken/body_parser_spec.rb +1 -2
  62. data/spec/shoryuken/client_spec.rb +1 -1
  63. data/spec/shoryuken/default_exception_handler_spec.rb +9 -10
  64. data/spec/shoryuken/default_worker_registry_spec.rb +1 -2
  65. data/spec/shoryuken/environment_loader_spec.rb +9 -8
  66. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +2 -1
  67. data/spec/shoryuken/extensions/active_job_base_spec.rb +2 -1
  68. data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +2 -1
  69. data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +2 -1
  70. data/spec/shoryuken/fetcher_spec.rb +23 -26
  71. data/spec/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
  72. data/spec/shoryuken/helpers/atomic_counter_spec.rb +177 -0
  73. data/spec/shoryuken/helpers/atomic_hash_spec.rb +307 -0
  74. data/spec/shoryuken/helpers/hash_utils_spec.rb +145 -0
  75. data/spec/shoryuken/helpers/string_utils_spec.rb +124 -0
  76. data/spec/shoryuken/helpers_integration_spec.rb +96 -0
  77. data/spec/shoryuken/inline_message_spec.rb +196 -0
  78. data/spec/shoryuken/launcher_spec.rb +1 -2
  79. data/spec/shoryuken/manager_spec.rb +1 -2
  80. data/spec/shoryuken/middleware/chain_spec.rb +1 -1
  81. data/spec/shoryuken/middleware/server/auto_delete_spec.rb +1 -1
  82. data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +1 -1
  83. data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +1 -1
  84. data/spec/shoryuken/middleware/server/timing_spec.rb +1 -1
  85. data/spec/shoryuken/options_spec.rb +4 -4
  86. data/spec/shoryuken/polling/base_strategy_spec.rb +280 -0
  87. data/spec/shoryuken/polling/queue_configuration_spec.rb +195 -0
  88. data/spec/shoryuken/polling/strict_priority_spec.rb +1 -1
  89. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +1 -1
  90. data/spec/shoryuken/processor_spec.rb +1 -1
  91. data/spec/shoryuken/queue_spec.rb +2 -3
  92. data/spec/shoryuken/runner_spec.rb +1 -3
  93. data/spec/shoryuken/util_spec.rb +1 -1
  94. data/spec/shoryuken/worker/default_executor_spec.rb +1 -1
  95. data/spec/shoryuken/worker/inline_executor_spec.rb +1 -1
  96. data/spec/shoryuken/worker_spec.rb +15 -11
  97. data/spec/shoryuken_spec.rb +1 -1
  98. data/spec/spec_helper.rb +16 -0
  99. metadata +60 -27
  100. data/.github/FUNDING.yml +0 -12
  101. data/gemfiles/rails_6_1.gemfile +0 -18
  102. data/lib/shoryuken/core_ext.rb +0 -69
  103. data/lib/shoryuken/polling/base.rb +0 -67
  104. data/shoryuken.jpg +0 -0
  105. data/spec/shoryuken/core_ext_spec.rb +0 -40
@@ -1,8 +1,5 @@
1
- require 'spec_helper'
2
- require 'shoryuken/manager'
3
- require 'shoryuken/fetcher'
1
+ # frozen_string_literal: true
4
2
 
5
- # rubocop:disable Metrics/BlockLength
6
3
  RSpec.describe Shoryuken::Fetcher do
7
4
  let(:queue) { instance_double('Shoryuken::Queue', fifo?: false) }
8
5
  let(:queue_name) { 'default' }
@@ -29,17 +26,17 @@ RSpec.describe Shoryuken::Fetcher do
29
26
  Shoryuken.sqs_client_receive_message_opts[group] = { wait_time_seconds: 10 }
30
27
 
31
28
  expect(queue).to receive(:receive_messages).with({
32
- wait_time_seconds: 10,
33
- max_number_of_messages: limit,
34
- message_attribute_names: ['All'],
35
- attribute_names: ['All']
36
- }).and_return([])
29
+ wait_time_seconds: 10,
30
+ max_number_of_messages: limit,
31
+ message_attribute_names: ['All'],
32
+ attribute_names: ['All']
33
+ }).and_return([])
37
34
 
38
35
  subject.fetch(queue_config, limit)
39
36
  end
40
37
 
41
38
  it 'logs debug only' do
42
- # See https://github.com/phstc/shoryuken/issues/435
39
+ # See https://github.com/ruby-shoryuken/shoryuken/issues/435
43
40
  logger = double 'logger'
44
41
 
45
42
  allow(subject).to receive(:logger).and_return(logger)
@@ -63,10 +60,10 @@ RSpec.describe Shoryuken::Fetcher do
63
60
  Shoryuken.sqs_client_receive_message_opts[queue_name] = { max_number_of_messages: 1 }
64
61
 
65
62
  expect(queue).to receive(:receive_messages).with({
66
- max_number_of_messages: 1,
67
- message_attribute_names: ['All'],
68
- attribute_names: ['All']
69
- }).and_return([])
63
+ max_number_of_messages: 1,
64
+ message_attribute_names: ['All'],
65
+ attribute_names: ['All']
66
+ }).and_return([])
70
67
 
71
68
  subject.fetch(queue_config, limit)
72
69
  end
@@ -79,10 +76,10 @@ RSpec.describe Shoryuken::Fetcher do
79
76
  Shoryuken.sqs_client_receive_message_opts[queue_name] = { max_number_of_messages: 20 }
80
77
 
81
78
  expect(queue).to receive(:receive_messages).with({
82
- max_number_of_messages: limit,
83
- message_attribute_names: ['All'],
84
- attribute_names: ['All']
85
- }).and_return([])
79
+ max_number_of_messages: limit,
80
+ message_attribute_names: ['All'],
81
+ attribute_names: ['All']
82
+ }).and_return([])
86
83
 
87
84
  subject.fetch(queue_config, limit)
88
85
  end
@@ -94,8 +91,8 @@ RSpec.describe Shoryuken::Fetcher do
94
91
  specify do
95
92
  allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
96
93
  expect(queue).to receive(:receive_messages).with({
97
- max_number_of_messages: described_class::FETCH_LIMIT, attribute_names: ['All'], message_attribute_names: ['All']
98
- }).and_return([])
94
+ max_number_of_messages: described_class::FETCH_LIMIT, attribute_names: ['All'], message_attribute_names: ['All']
95
+ }).and_return([])
99
96
 
100
97
  subject.fetch(queue_config, limit)
101
98
  end
@@ -106,26 +103,26 @@ RSpec.describe Shoryuken::Fetcher do
106
103
  let(:queue) { instance_double('Shoryuken::Queue', fifo?: true, name: queue_name) }
107
104
 
108
105
  it 'polls one message at a time' do
109
- # see https://github.com/phstc/shoryuken/pull/530
106
+ # see https://github.com/ruby-shoryuken/shoryuken/pull/530
110
107
 
111
108
  allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
112
109
  expect(queue).to receive(:receive_messages).with({
113
- max_number_of_messages: 1, attribute_names: ['All'], message_attribute_names: ['All']
114
- }).and_return([])
110
+ max_number_of_messages: 1, attribute_names: ['All'], message_attribute_names: ['All']
111
+ }).and_return([])
115
112
 
116
113
  subject.fetch(queue_config, limit)
117
114
  end
118
115
 
119
116
  context 'with batch=true' do
120
117
  it 'polls the provided limit' do
121
- # see https://github.com/phstc/shoryuken/pull/530
118
+ # see https://github.com/ruby-shoryuken/shoryuken/pull/530
122
119
 
123
120
  allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
124
121
  allow(Shoryuken.worker_registry).to receive(:batch_receive_messages?).with(queue.name).and_return(true)
125
122
 
126
123
  expect(queue).to receive(:receive_messages).with({
127
- max_number_of_messages: limit, attribute_names: ['All'], message_attribute_names: ['All']
128
- }).and_return([])
124
+ max_number_of_messages: limit, attribute_names: ['All'], message_attribute_names: ['All']
125
+ }).and_return([])
129
126
 
130
127
  subject.fetch(queue_config, limit)
131
128
  end
@@ -0,0 +1,196 @@
1
+ require 'spec_helper'
2
+ require 'shoryuken/helpers/atomic_boolean'
3
+
4
+ RSpec.describe Shoryuken::Helpers::AtomicBoolean do
5
+ subject { described_class.new }
6
+
7
+ describe '#initialize' do
8
+ it 'initializes with default value of false' do
9
+ boolean = described_class.new
10
+ expect(boolean.value).to eq(false)
11
+ end
12
+
13
+ it 'initializes with true value' do
14
+ boolean = described_class.new(true)
15
+ expect(boolean.value).to eq(true)
16
+ end
17
+
18
+ it 'initializes with false value' do
19
+ boolean = described_class.new(false)
20
+ expect(boolean.value).to eq(false)
21
+ end
22
+
23
+ it 'converts truthy values to true' do
24
+ boolean = described_class.new('truthy')
25
+ expect(boolean.value).to eq(true)
26
+ end
27
+
28
+ it 'converts falsy values to false' do
29
+ boolean = described_class.new(nil)
30
+ expect(boolean.value).to eq(false)
31
+ end
32
+ end
33
+
34
+ describe '#value' do
35
+ it 'returns the current value' do
36
+ expect(subject.value).to eq(false)
37
+ end
38
+
39
+ it 'returns the updated value after operations' do
40
+ subject.make_true
41
+ expect(subject.value).to eq(true)
42
+
43
+ subject.make_false
44
+ expect(subject.value).to eq(false)
45
+ end
46
+ end
47
+
48
+ describe '#make_true' do
49
+ it 'sets the value to true' do
50
+ expect { subject.make_true }.to change { subject.value }.from(false).to(true)
51
+ end
52
+
53
+ it 'returns true' do
54
+ result = subject.make_true
55
+ expect(result).to eq(true)
56
+ end
57
+
58
+ it 'keeps the value true if already true' do
59
+ subject.make_true
60
+ expect { subject.make_true }.not_to change { subject.value }
61
+ expect(subject.value).to eq(true)
62
+ end
63
+ end
64
+
65
+ describe '#make_false' do
66
+ it 'sets the value to false' do
67
+ subject.make_true # Start with true
68
+ expect { subject.make_false }.to change { subject.value }.from(true).to(false)
69
+ end
70
+
71
+ it 'returns false' do
72
+ result = subject.make_false
73
+ expect(result).to eq(false)
74
+ end
75
+
76
+ it 'keeps the value false if already false' do
77
+ expect { subject.make_false }.not_to change { subject.value }
78
+ expect(subject.value).to eq(false)
79
+ end
80
+ end
81
+
82
+ describe '#true?' do
83
+ it 'returns true when value is true' do
84
+ subject.make_true
85
+ expect(subject.true?).to eq(true)
86
+ end
87
+
88
+ it 'returns false when value is false' do
89
+ expect(subject.true?).to eq(false)
90
+ end
91
+ end
92
+
93
+ describe '#false?' do
94
+ it 'returns true when value is false' do
95
+ expect(subject.false?).to eq(true)
96
+ end
97
+
98
+ it 'returns false when value is true' do
99
+ subject.make_true
100
+ expect(subject.false?).to eq(false)
101
+ end
102
+ end
103
+
104
+ describe 'thread safety' do
105
+ it 'handles concurrent make_true operations correctly' do
106
+ boolean = described_class.new(false)
107
+ threads = []
108
+
109
+ 10.times do
110
+ threads << Thread.new do
111
+ 100.times { boolean.make_true }
112
+ end
113
+ end
114
+
115
+ threads.each(&:join)
116
+ expect(boolean.value).to eq(true)
117
+ end
118
+
119
+ it 'handles concurrent make_false operations correctly' do
120
+ boolean = described_class.new(true)
121
+ threads = []
122
+
123
+ 10.times do
124
+ threads << Thread.new do
125
+ 100.times { boolean.make_false }
126
+ end
127
+ end
128
+
129
+ threads.each(&:join)
130
+ expect(boolean.value).to eq(false)
131
+ end
132
+
133
+ it 'handles mixed concurrent operations correctly' do
134
+ boolean = described_class.new(false)
135
+ threads = []
136
+ results = []
137
+
138
+ # Multiple threads setting to true and false
139
+ 10.times do
140
+ threads << Thread.new do
141
+ 50.times do
142
+ boolean.make_true
143
+ boolean.make_false
144
+ end
145
+ end
146
+ end
147
+
148
+ # Reader threads
149
+ 5.times do
150
+ threads << Thread.new do
151
+ 100.times { results << boolean.value }
152
+ end
153
+ end
154
+
155
+ threads.each(&:join)
156
+
157
+ # All read values should be valid booleans
158
+ expect(results).to all(satisfy { |v| v == true || v == false })
159
+ end
160
+ end
161
+
162
+ describe 'drop-in replacement for Concurrent::AtomicBoolean' do
163
+ it 'provides the same basic API' do
164
+ # Test that our implementation has the same methods as Concurrent::AtomicBoolean
165
+ expect(subject).to respond_to(:value)
166
+ expect(subject).to respond_to(:make_true)
167
+ expect(subject).to respond_to(:make_false)
168
+ expect(subject).to respond_to(:true?)
169
+ expect(subject).to respond_to(:false?)
170
+ end
171
+
172
+ it 'does not expose counter-specific methods' do
173
+ expect(subject).not_to respond_to(:increment)
174
+ expect(subject).not_to respond_to(:decrement)
175
+ end
176
+
177
+ it 'behaves identically to Concurrent::AtomicBoolean for basic operations' do
178
+ # This test documents the expected behavior that matches Concurrent::AtomicBoolean
179
+ boolean = described_class.new(false)
180
+
181
+ expect(boolean.value).to eq(false)
182
+ expect(boolean.false?).to eq(true)
183
+ expect(boolean.true?).to eq(false)
184
+
185
+ boolean.make_true
186
+ expect(boolean.value).to eq(true)
187
+ expect(boolean.true?).to eq(true)
188
+ expect(boolean.false?).to eq(false)
189
+
190
+ boolean.make_false
191
+ expect(boolean.value).to eq(false)
192
+ expect(boolean.false?).to eq(true)
193
+ expect(boolean.true?).to eq(false)
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Shoryuken::Helpers::AtomicCounter do
4
+ subject { described_class.new }
5
+
6
+ describe '#initialize' do
7
+ it 'initializes with default value of 0' do
8
+ counter = described_class.new
9
+ expect(counter.value).to eq(0)
10
+ end
11
+
12
+ it 'initializes with custom value' do
13
+ counter = described_class.new(42)
14
+ expect(counter.value).to eq(42)
15
+ end
16
+
17
+ it 'initializes with negative value' do
18
+ counter = described_class.new(-10)
19
+ expect(counter.value).to eq(-10)
20
+ end
21
+ end
22
+
23
+ describe '#value' do
24
+ it 'returns the current value' do
25
+ expect(subject.value).to eq(0)
26
+ end
27
+
28
+ it 'returns the updated value after operations' do
29
+ subject.increment
30
+ expect(subject.value).to eq(1)
31
+
32
+ subject.decrement
33
+ expect(subject.value).to eq(0)
34
+ end
35
+ end
36
+
37
+ describe '#increment' do
38
+ it 'increments the counter by 1' do
39
+ expect { subject.increment }.to change { subject.value }.from(0).to(1)
40
+ end
41
+
42
+ it 'returns the new value' do
43
+ result = subject.increment
44
+ expect(result).to eq(1)
45
+ end
46
+
47
+ it 'can be called multiple times' do
48
+ 3.times { subject.increment }
49
+ expect(subject.value).to eq(3)
50
+ end
51
+
52
+ it 'works with negative initial values' do
53
+ counter = described_class.new(-5)
54
+ counter.increment
55
+ expect(counter.value).to eq(-4)
56
+ end
57
+ end
58
+
59
+ describe '#decrement' do
60
+ it 'decrements the counter by 1' do
61
+ subject.increment # Start at 1
62
+ expect { subject.decrement }.to change { subject.value }.from(1).to(0)
63
+ end
64
+
65
+ it 'returns the new value' do
66
+ subject.increment # Start at 1
67
+ result = subject.decrement
68
+ expect(result).to eq(0)
69
+ end
70
+
71
+ it 'can go negative' do
72
+ subject.decrement
73
+ expect(subject.value).to eq(-1)
74
+ end
75
+
76
+ it 'can be called multiple times' do
77
+ 3.times { subject.decrement }
78
+ expect(subject.value).to eq(-3)
79
+ end
80
+ end
81
+
82
+ describe 'thread safety' do
83
+ it 'handles concurrent increments correctly' do
84
+ counter = described_class.new
85
+ threads = []
86
+
87
+ 10.times do
88
+ threads << Thread.new do
89
+ 100.times { counter.increment }
90
+ end
91
+ end
92
+
93
+ threads.each(&:join)
94
+ expect(counter.value).to eq(1000)
95
+ end
96
+
97
+ it 'handles concurrent decrements correctly' do
98
+ counter = described_class.new(1000)
99
+ threads = []
100
+
101
+ 10.times do
102
+ threads << Thread.new do
103
+ 100.times { counter.decrement }
104
+ end
105
+ end
106
+
107
+ threads.each(&:join)
108
+ expect(counter.value).to eq(0)
109
+ end
110
+
111
+ it 'handles mixed concurrent operations correctly' do
112
+ counter = described_class.new
113
+ threads = []
114
+
115
+ # 5 threads incrementing
116
+ 5.times do
117
+ threads << Thread.new do
118
+ 100.times { counter.increment }
119
+ end
120
+ end
121
+
122
+ # 3 threads decrementing
123
+ 3.times do
124
+ threads << Thread.new do
125
+ 100.times { counter.decrement }
126
+ end
127
+ end
128
+
129
+ threads.each(&:join)
130
+ expect(counter.value).to eq(200) # 500 increments - 300 decrements
131
+ end
132
+
133
+ it 'provides atomic read operations' do
134
+ counter = described_class.new
135
+ values_read = []
136
+
137
+ # Writer thread
138
+ writer = Thread.new do
139
+ 1000.times { counter.increment }
140
+ end
141
+
142
+ # Reader threads
143
+ readers = 5.times.map do
144
+ Thread.new do
145
+ 100.times { values_read << counter.value }
146
+ end
147
+ end
148
+
149
+ [writer, *readers].each(&:join)
150
+
151
+ # All read values should be valid integers (not partial writes)
152
+ expect(values_read).to all(be_an(Integer))
153
+ expect(values_read).to all(be >= 0)
154
+ expect(values_read).to all(be <= 1000)
155
+ end
156
+ end
157
+
158
+ describe 'drop-in replacement for Concurrent::AtomicFixnum' do
159
+ it 'provides the same basic API' do
160
+ # Test that our implementation has the same methods as Concurrent::AtomicFixnum
161
+ expect(subject).to respond_to(:value)
162
+ expect(subject).to respond_to(:increment)
163
+ expect(subject).to respond_to(:decrement)
164
+ end
165
+
166
+ it 'behaves identically to Concurrent::AtomicFixnum for basic operations' do
167
+ # This test documents the expected behavior that matches Concurrent::AtomicFixnum
168
+ counter = described_class.new(5)
169
+
170
+ expect(counter.value).to eq(5)
171
+ expect(counter.increment).to eq(6)
172
+ expect(counter.value).to eq(6)
173
+ expect(counter.decrement).to eq(5)
174
+ expect(counter.value).to eq(5)
175
+ end
176
+ end
177
+ end