shoryuken 5.2.3 → 6.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f2f8c9e8573b699391a74a70d26756e106ccf72b3df9a6fccc32b72d13ec0cd
4
- data.tar.gz: 6a009d5a5e571da339d52a08f4ccda19f144ce06132f4a65e95fabc39aeffad4
3
+ metadata.gz: 47b6564c21a283ca5d0cd71f7f59b36b9c99776a541b2a51c61a9fd5b590be08
4
+ data.tar.gz: cf94b4ba4d9b296e0af112704cbe0ecd201f57726c284c790351f79a23680c7a
5
5
  SHA512:
6
- metadata.gz: e359eee09d82a917c4e13541a121c61f78587421a7e5977052552a4f18512eeb9a2eb720ebc8adf323d0a33fd979b37e9c059a95b5b180eda93cab5a3e034f6b
7
- data.tar.gz: 5217287d2b7f66125852ac0b824353d934a2a8568e2dc07ca3c98f8991cad1a0dafbda87967a407bc5efaea62b1cafa85708e1ea17ccb659b2743878d079a267
6
+ metadata.gz: d037b9887326e63ba1c8659a56e7707cf07b7a1a7d9f5027df7fb759d2896035335bc877487a346d7cac59e7b1916a8f5dc967e4431445b1e7ca9f6778282f7b
7
+ data.tar.gz: 4e0dd25a40a86a09ea87b0187d7f9f3d8525031b40830b47b29b434b688459e60209d7f8e4adb7d6c5deeedfb63b4139bceb0e38762b7fbf0dab884c1023063c
@@ -0,0 +1,17 @@
1
+ # [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 2.6, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 2.6-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster, 2.6-buster
2
+ ARG VARIANT=2-bullseye
3
+ FROM mcr.microsoft.com/vscode/devcontainers/ruby:0-${VARIANT}
4
+
5
+ # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
6
+ ARG NODE_VERSION="none"
7
+ RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
8
+
9
+ # [Optional] Uncomment this section to install additional OS packages.
10
+ # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
11
+ # && apt-get -y install --no-install-recommends <your-package-list-here>
12
+
13
+ # [Optional] Uncomment this line to install additional gems.
14
+ # RUN gem install <your-gem-names-here>
15
+
16
+ # [Optional] Uncomment this line to install global node packages.
17
+ # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
@@ -0,0 +1,43 @@
1
+ # [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 2.6, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 2.6-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster, 2.6-buster
2
+ ARG VARIANT=2-bullseye
3
+ FROM ruby:${VARIANT}
4
+
5
+ # Copy library scripts to execute
6
+ COPY library-scripts/*.sh library-scripts/*.env /tmp/library-scripts/
7
+
8
+ # [Option] Install zsh
9
+ ARG INSTALL_ZSH="true"
10
+ # [Option] Upgrade OS packages to their latest versions
11
+ ARG UPGRADE_PACKAGES="true"
12
+ # Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
13
+ ARG USERNAME=vscode
14
+ ARG USER_UID=1000
15
+ ARG USER_GID=$USER_UID
16
+ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
17
+ # Remove imagemagick due to https://security-tracker.debian.org/tracker/CVE-2019-10131
18
+ && apt-get purge -y imagemagick imagemagick-6-common \
19
+ # Install common packages, non-root user, rvm, core build tools
20
+ && bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \
21
+ && bash /tmp/library-scripts/ruby-debian.sh "none" "${USERNAME}" "true" "true" \
22
+ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*
23
+
24
+ # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
25
+ ARG NODE_VERSION="none"
26
+ ENV NVM_DIR=/usr/local/share/nvm
27
+ ENV NVM_SYMLINK_CURRENT=true \
28
+ PATH=${NVM_DIR}/current/bin:${PATH}
29
+ RUN bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}" \
30
+ && apt-get clean -y && rm -rf /var/lib/apt/lists/*
31
+
32
+ # Remove library scripts for final image
33
+ RUN rm -rf /tmp/library-scripts
34
+
35
+ # [Optional] Uncomment this section to install additional OS packages.
36
+ # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
37
+ # && apt-get -y install --no-install-recommends <your-package-list-here>
38
+
39
+ # [Optional] Uncomment this line to install additional gems.
40
+ # RUN gem install <your-gem-names-here>
41
+
42
+ # [Optional] Uncomment this line to install global node packages.
43
+ # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
@@ -0,0 +1,35 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2
+ // https://github.com/microsoft/vscode-dev-containers/tree/v0.224.3/containers/ruby
3
+ {
4
+ "name": "Ruby",
5
+ "build": {
6
+ "dockerfile": "Dockerfile",
7
+ "args": {
8
+ // Update 'VARIANT' to pick a Ruby version: 3, 3.1, 3.0, 2, 2.7, 2.6
9
+ // Append -bullseye or -buster to pin to an OS version.
10
+ // Use -bullseye variants on local on arm64/Apple Silicon.
11
+ "VARIANT": "3-bullseye",
12
+ // Options
13
+ "NODE_VERSION": "none"
14
+ }
15
+ },
16
+
17
+ // Set *default* container specific settings.json values on container create.
18
+ "settings": {},
19
+
20
+ // Add the IDs of extensions you want installed when the container is created.
21
+ "extensions": ["rebornix.Ruby"],
22
+
23
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
24
+ // "forwardPorts": [],
25
+
26
+ // Use 'postCreateCommand' to run commands after the container is created.
27
+ // "postCreateCommand": "ruby --version",
28
+
29
+ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
30
+ "remoteUser": "vscode",
31
+ "features": {
32
+ "github-cli": "latest",
33
+ "aws-cli": "latest"
34
+ }
35
+ }
@@ -0,0 +1,20 @@
1
+ name: "Close stale issues and PRs"
2
+ on:
3
+ schedule:
4
+ - cron: "30 1 * * *" # At 01:30 - https://crontab.guru/#30_1_*_*_*
5
+ workflow_dispatch: {}
6
+ jobs:
7
+ stale:
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ issues: write
11
+ pull-requests: write
12
+ steps:
13
+ - uses: actions/stale@v4
14
+ with:
15
+ stale-issue-message: This issue is now marked as stale because it hasn't seen activity for a while. Add a comment or it will be closed soon.
16
+ stale-pr-message: This PR is now marked as stale because it hasn't seen activity for a while. Add a comment or it will be closed soon.
17
+ close-issue-message: This issue was closed because it hasn't seen activity for a while.
18
+ close-pr-message: This PR was closed because it hasn't seen activity for a while.
19
+ days-before-stale: 60
20
+ days-before-close: 7
data/.gitignore CHANGED
@@ -25,4 +25,4 @@ shoryuken.yml
25
25
  *.log
26
26
  .env
27
27
  rubocop.html
28
- .byebug_history
28
+ .byebug_history
data/.rubocop.yml CHANGED
@@ -23,7 +23,7 @@ Metrics/AbcSize:
23
23
  # because codeclimate already give that for us with more details
24
24
  Enabled: false
25
25
 
26
- Metrics/LineLength:
26
+ Layout/LineLength:
27
27
  Max: 125
28
28
 
29
29
  Style/Alias:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## [v6.0.0] - 2022-02-18
2
+
3
+ - Breaking changes: Initialize Rails before parsing config file
4
+ - [#686](https://github.com/ruby-shoryuken/shoryuken/pull/686)
5
+ - Previously, Shoryuken read its configuration from an optional YAML file, then allowed CLI arguments to override those, then initialized the Rails application (provided that `--rails` or `-R` was specified). This behavior meant that the config file did not have access to things like environment variables that were initialized by Rails (such as when using `dotenv`). With this change, Rails is initialized much earlier in the process. After Rails is initialized, the YAML configuration file is interpreted, and CLI arguments are finally interpreted last. Most applications will not need to undergo changes in order to upgrade, but the new load order could technically result in different behavior depending on the application's YAML configuration file or Rails initializers.
6
+
7
+ ## [v5.3.2] - 2022-01-19
8
+
9
+ - (Bugfix) Preserve queue weights when unpausing queues
10
+ - [#687](https://github.com/ruby-shoryuken/shoryuken/pull/687)
11
+
12
+ - Improve error message on startup when shoryuken has insufficient permissions to access a queue
13
+ - [#691](https://github.com/ruby-shoryuken/shoryuken/pull/691)
14
+
15
+ ## [v5.3.1] - 2022-01-07
16
+
17
+ - (Bugfix) Fix issue where, when using the TSTP or USR1 signals for soft shutdowns, it was possible for shoryuken to terminate without first attempting to handle all messages it fetched from SQS
18
+ - [#676](https://github.com/ruby-shoryuken/shoryuken/pull/676)
19
+
20
+ ## [v5.3.0] - 2021-10-31
21
+
22
+ - (Refactor) Use Forwardable within Message to avoid method boilerplate
23
+ - [#681](https://github.com/ruby-shoryuken/shoryuken/pull/681)
24
+
25
+ - Add basic health check API
26
+ - [#679](https://github.com/ruby-shoryuken/shoryuken/pull/679)
27
+
1
28
  ## [v5.2.3] - 2021-07-29
2
29
 
3
30
  - Fire new `:utilization_update` event any time a worker pool's utilization changes
data/Gemfile CHANGED
@@ -16,5 +16,5 @@ end
16
16
  group :development do
17
17
  gem 'appraisal', git: 'https://github.com/thoughtbot/appraisal.git'
18
18
  gem 'pry-byebug', '3.9.0'
19
- gem 'rubocop'
19
+ gem 'rubocop', '<= 1.12'
20
20
  end
@@ -18,12 +18,12 @@ module Shoryuken
18
18
  end
19
19
 
20
20
  def setup_options
21
+ initialize_rails if load_rails?
21
22
  initialize_options
22
23
  initialize_logger
23
24
  end
24
25
 
25
26
  def load
26
- load_rails if Shoryuken.options[:rails]
27
27
  prefix_active_job_queue_names
28
28
  parse_queues
29
29
  require_workers
@@ -55,7 +55,7 @@ module Shoryuken
55
55
  Shoryuken.logger.level = Logger::DEBUG if Shoryuken.options[:verbose]
56
56
  end
57
57
 
58
- def load_rails
58
+ def initialize_rails
59
59
  # Adapted from: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/cli.rb
60
60
 
61
61
  require 'rails'
@@ -79,6 +79,10 @@ module Shoryuken
79
79
  end
80
80
  end
81
81
 
82
+ def load_rails?
83
+ options[:rails]
84
+ end
85
+
82
86
  def prefix_active_job_queue_name(queue_name, weight)
83
87
  return [queue_name, weight] if queue_name.start_with?('https://', 'arn:')
84
88
 
@@ -159,9 +163,17 @@ module Shoryuken
159
163
 
160
164
  return if non_existent_queues.none?
161
165
 
166
+ # NOTE: HEREDOC's ~ operator removes indents, but is only available Ruby 2.3+
167
+ # See github PR: https://github.com/ruby-shoryuken/shoryuken/pull/691#issuecomment-1007653595
168
+ error_msg = <<-MSG.gsub(/^\s+/, '')
169
+ The specified queue(s) #{non_existent_queues.join(', ')} do not exist.
170
+ Try 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings.
171
+ It's also possible that you don't have permission to access the specified queues.
172
+ MSG
173
+
162
174
  fail(
163
175
  ArgumentError,
164
- "The specified queue(s) #{non_existent_queues.join(', ')} do not exist.\nTry 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings"
176
+ error_msg
165
177
  )
166
178
  end
167
179
 
@@ -28,12 +28,30 @@ module Shoryuken
28
28
 
29
29
  initiate_stop
30
30
 
31
+ stop_new_dispatching
32
+ await_dispatching_in_progress
33
+
31
34
  executor.shutdown
32
35
  executor.wait_for_termination
33
36
  end
34
37
 
38
+ def healthy?
39
+ Shoryuken.groups.keys.all? do |group|
40
+ manager = @managers.find { |m| m.group == group }
41
+ manager && manager.running?
42
+ end
43
+ end
44
+
35
45
  private
36
46
 
47
+ def stop_new_dispatching
48
+ @managers.each(&:stop_new_dispatching)
49
+ end
50
+
51
+ def await_dispatching_in_progress
52
+ @managers.each(&:await_dispatching_in_progress)
53
+ end
54
+
37
55
  def executor
38
56
  @_executor ||= Shoryuken.launcher_executor || Concurrent.global_io_executor
39
57
  end
@@ -6,14 +6,18 @@ module Shoryuken
6
6
  # See https://github.com/phstc/shoryuken/issues/348#issuecomment-292847028
7
7
  MIN_DISPATCH_INTERVAL = 0.1
8
8
 
9
+ attr_reader :group
10
+
9
11
  def initialize(group, fetcher, polling_strategy, concurrency, executor)
10
- @group = group
11
- @fetcher = fetcher
12
- @polling_strategy = polling_strategy
13
- @max_processors = concurrency
14
- @busy_processors = Concurrent::AtomicFixnum.new(0)
15
- @executor = executor
16
- @running = Concurrent::AtomicBoolean.new(true)
12
+ @group = group
13
+ @fetcher = fetcher
14
+ @polling_strategy = polling_strategy
15
+ @max_processors = concurrency
16
+ @busy_processors = Concurrent::AtomicFixnum.new(0)
17
+ @executor = executor
18
+ @running = Concurrent::AtomicBoolean.new(true)
19
+ @stop_new_dispatching = Concurrent::AtomicBoolean.new(false)
20
+ @dispatching_release_signal = ::Queue.new
17
21
  end
18
22
 
19
23
  def start
@@ -21,14 +25,28 @@ module Shoryuken
21
25
  dispatch_loop
22
26
  end
23
27
 
24
- private
28
+ def stop_new_dispatching
29
+ @stop_new_dispatching.make_true
30
+ end
31
+
32
+ def await_dispatching_in_progress
33
+ # There might still be a dispatching on-going, as the response from SQS could take some time
34
+ # We don't want to stop the process before processing incoming messages, as they would stay "in-flight" for some time on SQS
35
+ # We use a queue, as the dispatch_loop is running on another thread, and this is a efficient way of communicating between threads.
36
+ @dispatching_release_signal.pop
37
+ end
25
38
 
26
39
  def running?
27
40
  @running.true? && @executor.running?
28
41
  end
29
42
 
43
+ private
44
+
30
45
  def dispatch_loop
31
- return unless running?
46
+ if @stop_new_dispatching.true? || !running?
47
+ @dispatching_release_signal << 1
48
+ return
49
+ end
32
50
 
33
51
  @executor.post { dispatch }
34
52
  end
@@ -92,7 +110,6 @@ module Shoryuken
92
110
 
93
111
  def dispatch_single_messages(queue)
94
112
  messages = @fetcher.fetch(queue, ready)
95
-
96
113
  @polling_strategy.messages_found(queue.name, messages.size)
97
114
  messages.each { |message| assign(queue.name, message) }
98
115
  end
@@ -1,5 +1,16 @@
1
1
  module Shoryuken
2
2
  class Message
3
+ extend Forwardable
4
+
5
+ def_delegators(:data,
6
+ :message_id,
7
+ :receipt_handle,
8
+ :md5_of_body,
9
+ :body,
10
+ :attributes,
11
+ :md5_of_message_attributes,
12
+ :message_attributes)
13
+
3
14
  attr_accessor :client, :queue_url, :queue_name, :data
4
15
 
5
16
  def initialize(client, queue, data)
@@ -29,33 +40,5 @@ module Shoryuken
29
40
  visibility_timeout: timeout
30
41
  )
31
42
  end
32
-
33
- def message_id
34
- data.message_id
35
- end
36
-
37
- def receipt_handle
38
- data.receipt_handle
39
- end
40
-
41
- def md5_of_body
42
- data.md5_of_body
43
- end
44
-
45
- def body
46
- data.body
47
- end
48
-
49
- def attributes
50
- data.attributes
51
- end
52
-
53
- def md5_of_message_attributes
54
- data.md5_of_message_attributes
55
- end
56
-
57
- def message_attributes
58
- data.message_attributes
59
- end
60
43
  end
61
44
  end
@@ -39,8 +39,10 @@ module Shoryuken
39
39
  end
40
40
 
41
41
  def message_processed(queue)
42
- logger.debug "Unpausing #{queue}"
43
- @paused_until[queue] = Time.now
42
+ if queue_paused?(queue)
43
+ logger.debug "Unpausing #{queue}"
44
+ @paused_until[queue] = Time.at 0
45
+ end
44
46
  end
45
47
 
46
48
  private
@@ -36,12 +36,10 @@ module Shoryuken
36
36
  end
37
37
 
38
38
  def message_processed(queue)
39
- return if @paused_queues.empty?
39
+ paused_queue = @paused_queues.find { |_time, name| name == queue }
40
+ return unless paused_queue
40
41
 
41
- logger.debug "Unpausing #{queue}"
42
- @paused_queues.reject! { |_time, name| name == queue }
43
- @queues << queue
44
- @queues.uniq!
42
+ paused_queue[0] = Time.at 0
45
43
  end
46
44
 
47
45
  private
@@ -30,9 +30,6 @@ module Shoryuken
30
30
 
31
31
  loader = EnvironmentLoader.setup_options(options)
32
32
 
33
- # When cli args exist, override options in config file
34
- Shoryuken.options.merge!(options)
35
-
36
33
  daemonize(Shoryuken.options)
37
34
  write_pid(Shoryuken.options)
38
35
 
@@ -55,6 +52,10 @@ module Shoryuken
55
52
  end
56
53
  end
57
54
 
55
+ def healthy?
56
+ (@launcher && @launcher.healthy?) || false
57
+ end
58
+
58
59
  private
59
60
 
60
61
  def initialize_concurrent_logger
@@ -1,3 +1,3 @@
1
1
  module Shoryuken
2
- VERSION = '5.2.3'.freeze
2
+ VERSION = '6.0.0'.freeze
3
3
  end
data/lib/shoryuken.rb CHANGED
@@ -45,6 +45,10 @@ module Shoryuken
45
45
  @_shoryuken_options ||= Shoryuken::Options.new
46
46
  end
47
47
 
48
+ def self.healthy?
49
+ Shoryuken::Runner.instance.healthy?
50
+ end
51
+
48
52
  def_delegators(
49
53
  :shoryuken_options,
50
54
  :active_job?,
@@ -4,11 +4,41 @@ require 'active_job'
4
4
  RSpec.describe Shoryuken::EnvironmentLoader do
5
5
  subject { described_class.new({}) }
6
6
 
7
- describe '#parse_queues loads default queues' do
7
+ describe '#load' do
8
8
  before do
9
+ Shoryuken.groups.clear
10
+ # See issue: https://stackoverflow.com/a/63699568 for stubbing AWS errors
11
+ allow(Shoryuken::Client)
12
+ .to receive(:queues)
13
+ .with('stubbed_queue')
14
+ .and_raise(Aws::SQS::Errors::NonExistentQueue.new(nil, nil))
9
15
  allow(subject).to receive(:load_rails)
10
16
  allow(subject).to receive(:prefix_active_job_queue_names)
11
17
  allow(subject).to receive(:require_workers)
18
+ allow(subject).to receive(:validate_workers)
19
+ allow(subject).to receive(:patch_deprecated_workers)
20
+ Shoryuken.options[:groups] = [['custom', { queues: ['stubbed_queue'] }]]
21
+ end
22
+
23
+ context "when given queues don't exist" do
24
+ specify do
25
+ expect { subject.load }.to raise_error(
26
+ ArgumentError,
27
+ <<-MSG.gsub(/^\s+/, '')
28
+ The specified queue(s) stubbed_queue do not exist.
29
+ Try 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings.
30
+ It's also possible that you don't have permission to access the specified queues.
31
+ MSG
32
+ )
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '#parse_queues loads default queues' do
38
+ before do
39
+ allow(subject).to receive(:initialize_rails)
40
+ allow(subject).to receive(:prefix_active_job_queue_names)
41
+ allow(subject).to receive(:require_workers)
12
42
  allow(subject).to receive(:validate_queues)
13
43
  allow(subject).to receive(:validate_workers)
14
44
  allow(subject).to receive(:patch_deprecated_workers)
@@ -24,7 +54,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
24
54
 
25
55
  describe '#parse_queues includes delay per groups' do
26
56
  before do
27
- allow(subject).to receive(:load_rails)
57
+ allow(subject).to receive(:initialize_rails)
28
58
  allow(subject).to receive(:prefix_active_job_queue_names)
29
59
  allow(subject).to receive(:require_workers)
30
60
  allow(subject).to receive(:validate_queues)
@@ -34,7 +64,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
34
64
 
35
65
  specify do
36
66
  Shoryuken.options[:queues] = ['queue1', 'queue2'] # default queues
37
- Shoryuken.options[:groups] = [[ 'custom', { queues: ['queue3'], delay: 25 }]]
67
+ Shoryuken.options[:groups] = [['custom', { queues: ['queue3'], delay: 25 }]]
38
68
  subject.load
39
69
 
40
70
  expect(Shoryuken.groups['default'][:queues]).to eq(%w[queue1 queue2])
@@ -44,10 +74,9 @@ RSpec.describe Shoryuken::EnvironmentLoader do
44
74
  end
45
75
  end
46
76
 
47
-
48
77
  describe '#prefix_active_job_queue_names' do
49
78
  before do
50
- allow(subject).to receive(:load_rails)
79
+ allow(subject).to receive(:initialize_rails)
51
80
  allow(subject).to receive(:require_workers)
52
81
  allow(subject).to receive(:validate_queues)
53
82
  allow(subject).to receive(:validate_workers)
@@ -76,7 +105,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
76
105
 
77
106
  it 'does not prefix url-based queues' do
78
107
  Shoryuken.options[:queues] = ['https://example.com/test_queue1']
79
- Shoryuken.options[:groups] = {'group1' => {queues: ['https://example.com/test_group1_queue1']}}
108
+ Shoryuken.options[:groups] = { 'group1' => { queues: ['https://example.com/test_group1_queue1'] } }
80
109
 
81
110
  subject.load
82
111
 
@@ -86,7 +115,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
86
115
 
87
116
  it 'does not prefix arn-based queues' do
88
117
  Shoryuken.options[:queues] = ['arn:aws:sqs:fake-region-1:1234:test_queue1']
89
- Shoryuken.options[:groups] = {'group1' => {queues: ['arn:aws:sqs:fake-region-1:1234:test_group1_queue1']}}
118
+ Shoryuken.options[:groups] = { 'group1' => { queues: ['arn:aws:sqs:fake-region-1:1234:test_group1_queue1'] } }
90
119
 
91
120
  subject.load
92
121
 
@@ -94,9 +123,11 @@ RSpec.describe Shoryuken::EnvironmentLoader do
94
123
  expect(Shoryuken.groups['group1'][:queues]).to(eq(['arn:aws:sqs:fake-region-1:1234:test_group1_queue1']))
95
124
  end
96
125
  end
126
+
97
127
  describe "#setup_options" do
98
- let (:cli_queues) { { "queue1"=> 10, "queue2" => 20 } }
99
- let (:config_queues) { [["queue1", 8], ["queue2", 4]] }
128
+ let(:cli_queues) { { "queue1" => 10, "queue2" => 20 } }
129
+ let(:config_queues) { [["queue1", 8], ["queue2", 4]] }
130
+
100
131
  context "when given queues through config and CLI" do
101
132
  specify do
102
133
  allow_any_instance_of(Shoryuken::EnvironmentLoader).to receive(:config_file_options).and_return({ queues: config_queues })
@@ -104,6 +135,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
104
135
  expect(Shoryuken.options[:queues]).to eq(cli_queues)
105
136
  end
106
137
  end
138
+
107
139
  context "when given queues through config only" do
108
140
  specify do
109
141
  allow_any_instance_of(Shoryuken::EnvironmentLoader).to receive(:config_file_options).and_return({ queues: config_queues })
@@ -111,6 +143,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
111
143
  expect(Shoryuken.options[:queues]).to eq(config_queues)
112
144
  end
113
145
  end
146
+
114
147
  context "when given queues through CLI only" do
115
148
  specify do
116
149
  Shoryuken::EnvironmentLoader.setup_options(queues: cli_queues)
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'shoryuken/launcher'
3
+
4
+ RSpec.describe Shoryuken::Launcher do
5
+ let(:executor) do
6
+ # We can't use Concurrent.global_io_executor in these tests since once you
7
+ # shut down a thread pool, you can't start it back up. Instead, we create
8
+ # one new thread pool executor for each spec. We use a new
9
+ # CachedThreadPool, since that most closely resembles
10
+ # Concurrent.global_io_executor
11
+ Concurrent::CachedThreadPool.new auto_terminate: true
12
+ end
13
+
14
+ let(:first_group_manager) { double(:first_group_manager, group: 'first_group') }
15
+ let(:second_group_manager) { double(:second_group_manager, group: 'second_group') }
16
+ let(:first_queue) { "launcher_spec_#{SecureRandom.uuid}" }
17
+ let(:second_queue) { "launcher_spec_#{SecureRandom.uuid}" }
18
+
19
+ before do
20
+ Shoryuken.add_group('first_group', 1)
21
+ Shoryuken.add_group('second_group', 1)
22
+ Shoryuken.add_queue(first_queue, 1, 'first_group')
23
+ Shoryuken.add_queue(second_queue, 1, 'second_group')
24
+ allow(Shoryuken).to receive(:launcher_executor).and_return(executor)
25
+ allow(Shoryuken::Manager).to receive(:new).with('first_group', any_args).and_return(first_group_manager)
26
+ allow(Shoryuken::Manager).to receive(:new).with('second_group', any_args).and_return(second_group_manager)
27
+ allow(first_group_manager).to receive(:running?).and_return(true)
28
+ allow(second_group_manager).to receive(:running?).and_return(true)
29
+ end
30
+
31
+ describe '#healthy?' do
32
+ context 'when all groups have managers' do
33
+ context 'when all managers are running' do
34
+ it 'returns true' do
35
+ expect(subject.healthy?).to be true
36
+ end
37
+ end
38
+
39
+ context 'when one manager is not running' do
40
+ before do
41
+ allow(second_group_manager).to receive(:running?).and_return(false)
42
+ end
43
+
44
+ it 'returns false' do
45
+ expect(subject.healthy?).to be false
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'when all groups do not have managers' do
51
+ before do
52
+ allow(second_group_manager).to receive(:group).and_return('some_random_group')
53
+ end
54
+
55
+ it 'returns false' do
56
+ expect(subject.healthy?).to be false
57
+ end
58
+ end
59
+ end
60
+ end
@@ -73,9 +73,11 @@ RSpec.describe Shoryuken::Manager do
73
73
  expect(subject).to receive(:fire_event).with(:dispatch, false, queue_name: q.name)
74
74
  expect(subject).to receive(:fire_event).with(:utilization_update,
75
75
  false,
76
- group: 'default',
77
- busy_processors: 1,
78
- max_processors: 1)
76
+ {
77
+ group: 'default',
78
+ busy_processors: 1,
79
+ max_processors: 1
80
+ })
79
81
  expect(Shoryuken::Processor).to receive(:process).with(q, message)
80
82
  expect(Shoryuken.logger).to receive(:info).never
81
83
 
@@ -106,9 +108,11 @@ RSpec.describe Shoryuken::Manager do
106
108
  expect(fetcher).to receive(:fetch).with(q, described_class::BATCH_LIMIT).and_return(messages)
107
109
  expect(subject).to receive(:fire_event).with(:utilization_update,
108
110
  false,
109
- group: 'default',
110
- busy_processors: 1,
111
- max_processors: 1)
111
+ {
112
+ group: 'default',
113
+ busy_processors: 1,
114
+ max_processors: 1
115
+ })
112
116
  expect(subject).to receive(:fire_event).with(:dispatch, false, queue_name: q.name)
113
117
  allow(subject).to receive(:batched_queue?).with(q).and_return(true)
114
118
  expect(Shoryuken::Processor).to receive(:process).with(q, messages)
@@ -173,4 +177,26 @@ RSpec.describe Shoryuken::Manager do
173
177
  end
174
178
  end
175
179
  end
180
+
181
+ describe '#running?' do
182
+ context 'when the executor is running' do
183
+ before do
184
+ allow(executor).to receive(:running?).and_return(true)
185
+ end
186
+
187
+ it 'returns true' do
188
+ expect(subject.running?).to be true
189
+ end
190
+ end
191
+
192
+ context 'when the executor is not running' do
193
+ before do
194
+ allow(executor).to receive(:running?).and_return(false)
195
+ end
196
+
197
+ it 'returns false' do
198
+ expect(subject.running?).to be false
199
+ end
200
+ end
201
+ end
176
202
  end
@@ -106,12 +106,37 @@ RSpec.describe Shoryuken::Polling::WeightedRoundRobin do
106
106
  end
107
107
 
108
108
  describe '#message_processed' do
109
- it 'removes paused queue, adds to active queues' do
110
- strategy = Shoryuken::Polling::WeightedRoundRobin.new([queue1, queue2])
111
- strategy.send(:pause, queue1)
112
- expect(strategy.active_queues).to eq([[queue2, 1]])
113
- strategy.message_processed(queue1)
114
- expect(strategy.active_queues).to eq([[queue2, 1], [queue1, 1]])
109
+ it 'removes delay from paused queue' do
110
+ queues << queue1
111
+ queues << queue2
112
+
113
+ expect(subject.next_queue).to eq(queue1)
114
+ subject.messages_found(queue1, 0) # pauses queue1
115
+
116
+ expect(subject.active_queues).to eq([[queue2, 1]])
117
+
118
+ subject.message_processed(queue1) # marks queue1 to be unpaused
119
+
120
+ expect(subject.next_queue).to eq(queue2) # implicitly unpauses queue1
121
+ expect(subject.active_queues).to eq([[queue1, 1], [queue2, 1]])
122
+ end
123
+
124
+ it 'preserves weight of queues when unpausing' do
125
+ queues << queue1
126
+ queues << queue1
127
+ queues << queue2
128
+
129
+ expect(subject.next_queue).to eq(queue1)
130
+ subject.messages_found(queue1, 1)
131
+
132
+ expect(subject.next_queue).to eq(queue2)
133
+ subject.messages_found(queue2, 0) # pauses queue2
134
+
135
+ expect(subject.active_queues).to eq([[queue1, 2]])
136
+ subject.message_processed(queue2) # marks queue2 to be unpaused
137
+
138
+ expect(subject.next_queue).to eq(queue1) # implicitly unpauses queue2
139
+ expect(subject.active_queues).to eq([[queue1, 2], [queue2, 1]])
115
140
  end
116
141
  end
117
142
  end
@@ -80,7 +80,8 @@ RSpec.describe Shoryuken::Queue do
80
80
  end
81
81
 
82
82
  it 'deletes' do
83
- expect(sqs).to receive(:delete_message_batch).with(entries: entries, queue_url: queue_url).and_return(double(failed: []))
83
+ expect(sqs).to receive(:delete_message_batch).with({ entries: entries,
84
+ queue_url: queue_url }).and_return(double(failed: []))
84
85
 
85
86
  subject.delete_messages(entries: entries)
86
87
  end
@@ -91,7 +92,8 @@ RSpec.describe Shoryuken::Queue do
91
92
  logger = double 'Logger'
92
93
 
93
94
  expect(sqs).to(
94
- receive(:delete_message_batch).with(entries: entries, queue_url: queue_url).and_return(double(failed: [failure]))
95
+ receive(:delete_message_batch).with({ entries: entries,
96
+ queue_url: queue_url }).and_return(double(failed: [failure]))
95
97
  )
96
98
  expect(subject).to receive(:logger).and_return(logger)
97
99
  expect(logger).to receive(:error)
@@ -157,7 +159,8 @@ RSpec.describe Shoryuken::Queue do
157
159
  it 'accepts SQS request parameters' do
158
160
  # https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message_batch-instance_method
159
161
  expect(sqs).to(
160
- receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1' }, { id: '1', message_body: 'msg2' }]))
162
+ receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1' },
163
+ { id: '1', message_body: 'msg2' }]))
161
164
  )
162
165
 
163
166
  subject.send_messages(entries: [{ id: '0', message_body: 'msg1' }, { id: '1', message_body: 'msg2' }])
@@ -286,7 +289,8 @@ RSpec.describe Shoryuken::Queue do
286
289
  Shoryuken.cache_visibility_timeout = false
287
290
 
288
291
  expect(sqs).to(
289
- receive(:get_queue_attributes).with(queue_url: queue_url, attribute_names: ['All']).and_return(attribute_response).exactly(3).times
292
+ receive(:get_queue_attributes).with(queue_url: queue_url,
293
+ attribute_names: ['All']).and_return(attribute_response).exactly(3).times
290
294
  )
291
295
  expect(subject.visibility_timeout).to eq(30)
292
296
  expect(subject.visibility_timeout).to eq(30)
@@ -299,7 +303,8 @@ RSpec.describe Shoryuken::Queue do
299
303
  Shoryuken.cache_visibility_timeout = true
300
304
 
301
305
  expect(sqs).to(
302
- receive(:get_queue_attributes).with(queue_url: queue_url, attribute_names: ['All']).and_return(attribute_response).once
306
+ receive(:get_queue_attributes).with({ queue_url: queue_url,
307
+ attribute_names: ['All'] }).and_return(attribute_response).once
303
308
  )
304
309
  expect(subject.visibility_timeout).to eq(30)
305
310
  expect(subject.visibility_timeout).to eq(30)
@@ -10,16 +10,16 @@ RSpec.describe Shoryuken::Worker::DefaultExecutor do
10
10
 
11
11
  describe '.perform_in' do
12
12
  it 'delays a message' do
13
- expect(sqs_queue).to receive(:send_message).with(
14
- message_attributes: {
15
- 'shoryuken_class' => {
16
- string_value: TestWorker.to_s,
17
- data_type: 'String'
18
- }
19
- },
20
- message_body: 'message',
21
- delay_seconds: 60
22
- )
13
+ expect(sqs_queue).to receive(:send_message).with({
14
+ message_attributes: {
15
+ 'shoryuken_class' => {
16
+ string_value: TestWorker.to_s,
17
+ data_type: 'String'
18
+ }
19
+ },
20
+ message_body: 'message',
21
+ delay_seconds: 60
22
+ })
23
23
 
24
24
  TestWorker.perform_in(60, 'message')
25
25
  end
@@ -33,16 +33,16 @@ RSpec.describe Shoryuken::Worker::DefaultExecutor do
33
33
 
34
34
  describe '.perform_at' do
35
35
  it 'delays a message' do
36
- expect(sqs_queue).to receive(:send_message).with(
37
- message_attributes: {
38
- 'shoryuken_class' => {
39
- string_value: TestWorker.to_s,
40
- data_type: 'String'
41
- }
42
- },
43
- message_body: 'message',
44
- delay_seconds: 60
45
- )
36
+ expect(sqs_queue).to receive(:send_message).with({
37
+ message_attributes: {
38
+ 'shoryuken_class' => {
39
+ string_value: TestWorker.to_s,
40
+ data_type: 'String'
41
+ }
42
+ },
43
+ message_body: 'message',
44
+ delay_seconds: 60
45
+ })
46
46
 
47
47
  TestWorker.perform_in(Time.now + 60, 'message')
48
48
  end
@@ -56,30 +56,30 @@ RSpec.describe Shoryuken::Worker::DefaultExecutor do
56
56
 
57
57
  describe '.perform_async' do
58
58
  it 'enqueues a message' do
59
- expect(sqs_queue).to receive(:send_message).with(
60
- message_attributes: {
61
- 'shoryuken_class' => {
62
- string_value: TestWorker.to_s,
63
- data_type: 'String'
64
- }
65
- },
66
- message_body: 'message'
67
- )
59
+ expect(sqs_queue).to receive(:send_message).with({
60
+ message_attributes: {
61
+ 'shoryuken_class' => {
62
+ string_value: TestWorker.to_s,
63
+ data_type: 'String'
64
+ }
65
+ },
66
+ message_body: 'message'
67
+ })
68
68
 
69
69
  TestWorker.perform_async('message')
70
70
  end
71
71
 
72
72
  it 'enqueues a message with options' do
73
- expect(sqs_queue).to receive(:send_message).with(
74
- delay_seconds: 60,
75
- message_attributes: {
76
- 'shoryuken_class' => {
77
- string_value: TestWorker.to_s,
78
- data_type: 'String'
79
- }
80
- },
81
- message_body: 'delayed message'
82
- )
73
+ expect(sqs_queue).to receive(:send_message).with({
74
+ delay_seconds: 60,
75
+ message_attributes: {
76
+ 'shoryuken_class' => {
77
+ string_value: TestWorker.to_s,
78
+ data_type: 'String'
79
+ }
80
+ },
81
+ message_body: 'delayed message'
82
+ })
83
83
 
84
84
  TestWorker.perform_async('delayed message', delay_seconds: 60)
85
85
  end
@@ -89,15 +89,15 @@ RSpec.describe Shoryuken::Worker::DefaultExecutor do
89
89
 
90
90
  expect(Shoryuken::Client).to receive(:queues).with(new_queue).and_return(sqs_queue)
91
91
 
92
- expect(sqs_queue).to receive(:send_message).with(
93
- message_attributes: {
94
- 'shoryuken_class' => {
95
- string_value: TestWorker.to_s,
96
- data_type: 'String'
97
- }
98
- },
99
- message_body: 'delayed message'
100
- )
92
+ expect(sqs_queue).to receive(:send_message).with({
93
+ message_attributes: {
94
+ 'shoryuken_class' => {
95
+ string_value: TestWorker.to_s,
96
+ data_type: 'String'
97
+ }
98
+ },
99
+ message_body: 'delayed message'
100
+ })
101
101
 
102
102
  TestWorker.perform_async('delayed message', queue: new_queue)
103
103
  end
@@ -1,4 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Shoryuken do
4
+ describe '.healthy?' do
5
+ before do
6
+ allow(Shoryuken::Runner).to receive(:instance).and_return(double(:instance, healthy?: :some_result))
7
+ end
8
+
9
+ it 'delegates to the runner instance' do
10
+ expect(described_class.healthy?).to eq(:some_result)
11
+ end
12
+ end
4
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoryuken
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.3
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Cantero
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-30 00:00:00.000000000 Z
11
+ date: 2022-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -103,8 +103,12 @@ extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
105
  - ".codeclimate.yml"
106
+ - ".devcontainer/Dockerfile"
107
+ - ".devcontainer/base.Dockerfile"
108
+ - ".devcontainer/devcontainer.json"
106
109
  - ".github/FUNDING.yml"
107
110
  - ".github/workflows/specs.yml"
111
+ - ".github/workflows/stale.yml"
108
112
  - ".gitignore"
109
113
  - ".reek.yml"
110
114
  - ".rspec"
@@ -174,6 +178,7 @@ files:
174
178
  - spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb
175
179
  - spec/shoryuken/extensions/active_job_wrapper_spec.rb
176
180
  - spec/shoryuken/fetcher_spec.rb
181
+ - spec/shoryuken/launcher_spec.rb
177
182
  - spec/shoryuken/manager_spec.rb
178
183
  - spec/shoryuken/middleware/chain_spec.rb
179
184
  - spec/shoryuken/middleware/server/auto_delete_spec.rb
@@ -213,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
218
  - !ruby/object:Gem::Version
214
219
  version: '0'
215
220
  requirements: []
216
- rubygems_version: 3.0.1
221
+ rubygems_version: 3.1.2
217
222
  signing_key:
218
223
  specification_version: 4
219
224
  summary: Shoryuken is a super efficient AWS SQS thread based message processor
@@ -231,6 +236,7 @@ test_files:
231
236
  - spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb
232
237
  - spec/shoryuken/extensions/active_job_wrapper_spec.rb
233
238
  - spec/shoryuken/fetcher_spec.rb
239
+ - spec/shoryuken/launcher_spec.rb
234
240
  - spec/shoryuken/manager_spec.rb
235
241
  - spec/shoryuken/middleware/chain_spec.rb
236
242
  - spec/shoryuken/middleware/server/auto_delete_spec.rb