shoryuken 5.1.1 → 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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +17 -0
  3. data/.devcontainer/base.Dockerfile +43 -0
  4. data/.devcontainer/devcontainer.json +35 -0
  5. data/.github/workflows/specs.yml +10 -5
  6. data/.github/workflows/stale.yml +20 -0
  7. data/.gitignore +1 -1
  8. data/.rubocop.yml +1 -1
  9. data/Appraisals +8 -0
  10. data/CHANGELOG.md +57 -0
  11. data/Gemfile +1 -1
  12. data/README.md +8 -2
  13. data/Rakefile +14 -3
  14. data/bin/cli/sqs.rb +50 -5
  15. data/gemfiles/aws_sdk_core_2.gemfile +21 -0
  16. data/lib/shoryuken/environment_loader.rb +17 -3
  17. data/lib/shoryuken/extensions/active_job_adapter.rb +4 -2
  18. data/lib/shoryuken/launcher.rb +19 -0
  19. data/lib/shoryuken/manager.rb +40 -10
  20. data/lib/shoryuken/message.rb +11 -28
  21. data/lib/shoryuken/options.rb +1 -0
  22. data/lib/shoryuken/polling/strict_priority.rb +4 -2
  23. data/lib/shoryuken/polling/weighted_round_robin.rb +3 -5
  24. data/lib/shoryuken/runner.rb +4 -3
  25. data/lib/shoryuken/version.rb +1 -1
  26. data/lib/shoryuken.rb +4 -0
  27. data/spec/integration/launcher_spec.rb +29 -2
  28. data/spec/shoryuken/environment_loader_spec.rb +62 -9
  29. data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +4 -0
  30. data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +20 -0
  31. data/spec/shoryuken/launcher_spec.rb +60 -0
  32. data/spec/shoryuken/manager_spec.rb +37 -1
  33. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +31 -6
  34. data/spec/shoryuken/queue_spec.rb +10 -5
  35. data/spec/shoryuken/worker/default_executor_spec.rb +48 -48
  36. data/spec/shoryuken_spec.rb +9 -0
  37. data/spec/spec_helper.rb +0 -4
  38. metadata +12 -4
  39. data/Gemfile.aws-sdk-core-v2 +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d08b7287c9dcb2c488715f1313e11d9a9645c068d90fc160c76dbc57ff5d795
4
- data.tar.gz: 1e6009538535c438a02267e948d5f3eacaedf4aa1283950733bfc51972dcf872
3
+ metadata.gz: 47b6564c21a283ca5d0cd71f7f59b36b9c99776a541b2a51c61a9fd5b590be08
4
+ data.tar.gz: cf94b4ba4d9b296e0af112704cbe0ecd201f57726c284c790351f79a23680c7a
5
5
  SHA512:
6
- metadata.gz: dfc89030844c19828865a6495a4dcf4ef8e46f361303c4c906a0f1d56b2e9fdc46baebcd9eb6ee826a65a173dde4ed2d1a2e1ebc8dae64a8ed131fc5ebe65f13
7
- data.tar.gz: af2ac18ce6d1445fd261344220b72271d78e10c4034bf565c782d4baad44f946459348342b1fbcbaa75fbe8984ef2655aad78cad2a7128feee5603466fc736d5
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
+ }
@@ -9,9 +9,14 @@ jobs:
9
9
  name: All Specs
10
10
  strategy:
11
11
  matrix:
12
- ruby: ['2.4.4', '2.5.1', '2.6.3']
13
- gemfile: ['Gemfile', 'Gemfile.aws-sdk-core-v2']
12
+ ruby: ['2.4', '2.5', '2.6', '2.7', '3.0']
13
+ gemfile: ['Gemfile', 'gemfiles/aws_sdk_core_2.gemfile']
14
14
  runs-on: ubuntu-20.04
15
+ services:
16
+ moto_sqs:
17
+ image: quay.io/cjlarose/moto-sqs-server:1.1.0
18
+ ports:
19
+ - 5000:5000
15
20
  env:
16
21
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
17
22
  steps:
@@ -22,9 +27,9 @@ jobs:
22
27
  ruby-version: ${{ matrix.ruby }}
23
28
  bundler-cache: true
24
29
  - name: Run specs
25
- env:
26
- SPEC_ALL: true
27
30
  run: bundle exec rake spec
31
+ - name: Run integration specs
32
+ run: bundle exec rake spec:integration
28
33
  rails_specs:
29
34
  name: Rails Specs
30
35
  strategy:
@@ -54,4 +59,4 @@ jobs:
54
59
  ruby-version: ${{ matrix.ruby }}
55
60
  bundler-cache: true
56
61
  - name: Run Rails specs
57
- run: bundle exec rake rails_specs
62
+ run: bundle exec rake spec:rails
@@ -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/Appraisals CHANGED
@@ -1,3 +1,11 @@
1
+ appraise 'aws_sdk_core_2' do
2
+ group :test do
3
+ gem 'aws-sdk-core', '~> 2'
4
+ gem 'webrick' # required for Ruby 3
5
+ remove_gem 'aws-sdk-sqs'
6
+ end
7
+ end
8
+
1
9
  appraise 'rails_4_2' do
2
10
  group :test do
3
11
  gem 'activejob', '~> 4.2'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,60 @@
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
+
28
+ ## [v5.2.3] - 2021-07-29
29
+
30
+ - Fire new `:utilization_update` event any time a worker pool's utilization changes
31
+ - [#673](https://github.com/ruby-shoryuken/shoryuken/pull/673)
32
+
33
+ ## [v5.2.2] - 2021-06-22
34
+
35
+ - When using ActiveJob queue name prefixing, avoid applying prefix to queues configured with a URL or ARN
36
+ - [#667](https://github.com/ruby-shoryuken/shoryuken/pull/667)
37
+
38
+ ## [v5.2.1] - 2021-04-06
39
+
40
+ - Reduce message batch sizes in `shoryuken sqs requeue` and `shoryuken sqs mv` commands
41
+ - [#666](https://github.com/ruby-shoryuken/shoryuken/pull/666)
42
+
43
+ - Fix bug in `shoryuken sqs requeue` and `shoryuken sqs mv` where those commands would exceed the SQS `SendMessageBatch` maximum payload size
44
+ - [#663](https://github.com/ruby-shoryuken/shoryuken/issues/663)
45
+ - [#664](https://github.com/ruby-shoryuken/shoryuken/pull/664)
46
+
47
+ - Remove test stub for `Concurrent.global_io_executor`
48
+ - [#662](https://github.com/ruby-shoryuken/shoryuken/pull/662)
49
+
50
+ - Run integration tests on CI
51
+ - [#660](https://github.com/ruby-shoryuken/shoryuken/pull/660)
52
+
53
+ ## [v5.2.0] - 2021-02-26
54
+
55
+ - Set `executions` correctly for ActiveJob jobs
56
+ - [#657](https://github.com/phstc/shoryuken/pull/657)
57
+
1
58
  ## [v5.1.1] - 2021-02-10
2
59
 
3
60
  - Fix regression in Ruby 3.0 introduced in Shoryuken 5.1.0, where enqueueing jobs with ActiveJob to workers that used keyword arguments would fail
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
data/README.md CHANGED
@@ -69,7 +69,7 @@ For more information check the [wiki page](https://github.com/phstc/shoryuken/wi
69
69
 
70
70
  ### Testing
71
71
 
72
- To run all specs against the latest dependency vesions, execute
72
+ To run all unit specs against the latest dependency vesions, execute
73
73
 
74
74
  ```sh
75
75
  bundle exec rake spec
@@ -78,5 +78,11 @@ bundle exec rake spec
78
78
  To run all Rails-related specs against all supported versions of Rails, execute
79
79
 
80
80
  ```sh
81
- bundle exec appraisal rake rails_specs
81
+ bundle exec appraisal rake spec:rails
82
+ ```
83
+
84
+ To run integration specs, start a mock SQS server on `localhost:5000`. One such option is [cjlarose/moto-sqs-server](https://github.com/cjlarose/moto-sqs-server). Then execute
85
+
86
+ ```sh
87
+ bundle exec rake spec:integration
82
88
  ```
data/Rakefile CHANGED
@@ -3,10 +3,21 @@ $stdout.sync = true
3
3
 
4
4
  begin
5
5
  require 'rspec/core/rake_task'
6
- RSpec::Core::RakeTask.new(:spec)
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.exclude_pattern = 'spec/integration/**/*_spec.rb'
8
+ end
9
+
10
+ namespace :spec do
11
+ desc 'Run Rails specs only'
12
+ RSpec::Core::RakeTask.new(:rails) do |t|
13
+ t.pattern = 'spec/shoryuken/{environment_loader_spec,extensions/active_job_*}.rb'
14
+ end
7
15
 
8
- rails_task = RSpec::Core::RakeTask.new(:rails_specs)
9
- rails_task.pattern = 'spec/shoryuken/{environment_loader_spec,extensions/active_job_*}.rb'
16
+ desc 'Run integration specs only'
17
+ RSpec::Core::RakeTask.new(:integration) do |t|
18
+ t.pattern = 'spec/integration/**/*_spec.rb'
19
+ end
20
+ end
10
21
  rescue LoadError
11
22
  end
12
23
 
data/bin/cli/sqs.rb CHANGED
@@ -4,6 +4,9 @@ require 'date'
4
4
  module Shoryuken
5
5
  module CLI
6
6
  class SQS < Base
7
+ # See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html
8
+ MAX_BATCH_SIZE = 256 * 1024
9
+
7
10
  namespace :sqs
8
11
  class_option :endpoint, aliases: '-e', type: :string, default: ENV['SHORYUKEN_SQS_ENDPOINT'], desc: 'Endpoint URL'
9
12
 
@@ -51,14 +54,56 @@ module Shoryuken
51
54
  end
52
55
  end
53
56
 
54
- def batch_send(url, messages, messages_per_batch = 10)
55
- messages.to_a.flatten.map(&method(:normalize_dump_message)).each_slice(messages_per_batch) do |batch|
56
- sqs.send_message_batch(queue_url: url, entries: batch).failed.any? do |failure|
57
- say "Could not requeue #{failure.id}, code: #{failure.code}", :yellow
57
+ def batch_send(url, messages, max_batch_size = 10)
58
+ messages = messages.to_a.flatten.map(&method(:normalize_dump_message))
59
+ batch_send_normalized_messages url, messages, max_batch_size
60
+ end
61
+
62
+ def batch_send_normalized_messages(url, messages, max_batch_size)
63
+ # Repeatedly take the longest prefix of messages such that
64
+ # 1. The number of messages is less than or equal to max_batch_size
65
+ # 2. The total message payload size is less than or equal to the
66
+ # batch payload limit
67
+ while messages.size.positive?
68
+ batch_size = max_batch_size
69
+ loop do
70
+ batch = messages.take batch_size
71
+
72
+ unless batch.size == 1 || batch_payload_size(batch) <= MAX_BATCH_SIZE
73
+ batch_size = batch.size - 1
74
+ next
75
+ end
76
+
77
+ sqs.send_message_batch(queue_url: url, entries: batch).failed.any? do |failure|
78
+ say "Could not requeue #{failure.id}, code: #{failure.code}", :yellow
79
+ end
80
+ messages = messages.drop batch.size
81
+ break
58
82
  end
59
83
  end
60
84
  end
61
85
 
86
+ def batch_payload_size(messages)
87
+ messages.sum(&method(:message_size))
88
+ end
89
+
90
+ def message_size(message)
91
+ attribute_size = (message[:message_attributes] || []).sum do |name, value|
92
+ name_size = name.to_s.bytesize
93
+ data_type_size = value[:data_type].bytesize
94
+ value_size = if value[:string_value]
95
+ value[:string_value].bytesize
96
+ elsif value[:binary_value]
97
+ value[:binary_value].bytesize
98
+ end
99
+ name_size + data_type_size + value_size
100
+ end
101
+
102
+ body_size = message[:message_body].bytesize
103
+
104
+ attribute_size + body_size
105
+ end
106
+
62
107
  def find_all(url, limit)
63
108
  count = 0
64
109
  batch_size = limit > 10 ? 10 : limit
@@ -160,7 +205,7 @@ module Shoryuken
160
205
  end
161
206
 
162
207
  desc 'requeue QUEUE-NAME PATH', 'Requeues messages from a dump file'
163
- method_option :batch_size, aliases: '-n', type: :numeric, default: 10, desc: 'number of messages per batch to send'
208
+ method_option :batch_size, aliases: '-n', type: :numeric, default: 10, desc: 'maximum number of messages per batch to send'
164
209
  def requeue(queue_name, path)
165
210
  fail_task "Path #{path} not found" unless File.exist?(path)
166
211
 
@@ -0,0 +1,21 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ group :test do
6
+ gem "activejob"
7
+ gem "aws-sdk-core", "~> 2"
8
+ gem "codeclimate-test-reporter", require: nil
9
+ gem "httparty"
10
+ gem "multi_xml"
11
+ gem "simplecov"
12
+ gem "webrick"
13
+ end
14
+
15
+ group :development do
16
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
17
+ gem "pry-byebug", "3.9.0"
18
+ gem "rubocop"
19
+ end
20
+
21
+ gemspec path: "../"
@@ -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,7 +79,13 @@ 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)
87
+ return [queue_name, weight] if queue_name.start_with?('https://', 'arn:')
88
+
83
89
  queue_name_prefix = ::ActiveJob::Base.queue_name_prefix
84
90
  queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
85
91
 
@@ -157,9 +163,17 @@ module Shoryuken
157
163
 
158
164
  return if non_existent_queues.none?
159
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
+
160
174
  fail(
161
175
  ArgumentError,
162
- "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
163
177
  )
164
178
  end
165
179
 
@@ -82,8 +82,10 @@ module ActiveJob
82
82
 
83
83
  shoryuken_options body_parser: :json, auto_delete: true
84
84
 
85
- def perform(_sqs_msg, hash)
86
- Base.execute hash
85
+ def perform(sqs_msg, hash)
86
+ receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
87
+ past_receives = receive_count - 1
88
+ Base.execute hash.merge({ 'executions' => past_receives })
87
89
  end
88
90
  end
89
91
 
@@ -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
@@ -71,6 +89,7 @@ module Shoryuken
71
89
  def create_managers
72
90
  Shoryuken.groups.map do |group, options|
73
91
  Shoryuken::Manager.new(
92
+ group,
74
93
  Shoryuken::Fetcher.new(group),
75
94
  Shoryuken.polling_strategy(group).new(options[:queues], Shoryuken.delay(group)),
76
95
  options[:concurrency],
@@ -6,27 +6,47 @@ module Shoryuken
6
6
  # See https://github.com/phstc/shoryuken/issues/348#issuecomment-292847028
7
7
  MIN_DISPATCH_INTERVAL = 0.1
8
8
 
9
- def initialize(fetcher, polling_strategy, concurrency, executor)
10
- @fetcher = fetcher
11
- @polling_strategy = polling_strategy
12
- @max_processors = concurrency
13
- @busy_processors = Concurrent::AtomicFixnum.new(0)
14
- @executor = executor
15
- @running = Concurrent::AtomicBoolean.new(true)
9
+ attr_reader :group
10
+
11
+ def initialize(group, fetcher, polling_strategy, concurrency, executor)
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
16
21
  end
17
22
 
18
23
  def start
24
+ fire_utilization_update_event
19
25
  dispatch_loop
20
26
  end
21
27
 
22
- 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
23
38
 
24
39
  def running?
25
40
  @running.true? && @executor.running?
26
41
  end
27
42
 
43
+ private
44
+
28
45
  def dispatch_loop
29
- return unless running?
46
+ if @stop_new_dispatching.true? || !running?
47
+ @dispatching_release_signal << 1
48
+ return
49
+ end
30
50
 
31
51
  @executor.post { dispatch }
32
52
  end
@@ -59,6 +79,8 @@ module Shoryuken
59
79
 
60
80
  def processor_done(queue)
61
81
  @busy_processors.decrement
82
+ fire_utilization_update_event
83
+
62
84
  client_queue = Shoryuken::Client.queues(queue)
63
85
  return unless client_queue.fifo?
64
86
  return unless @polling_strategy.respond_to?(:message_processed)
@@ -72,6 +94,7 @@ module Shoryuken
72
94
  logger.debug { "Assigning #{sqs_msg.message_id}" }
73
95
 
74
96
  @busy_processors.increment
97
+ fire_utilization_update_event
75
98
 
76
99
  Concurrent::Promise
77
100
  .execute(executor: @executor) { Processor.process(queue_name, sqs_msg) }
@@ -87,7 +110,6 @@ module Shoryuken
87
110
 
88
111
  def dispatch_single_messages(queue)
89
112
  messages = @fetcher.fetch(queue, ready)
90
-
91
113
  @polling_strategy.messages_found(queue.name, messages.size)
92
114
  messages.each { |message| assign(queue.name, message) }
93
115
  end
@@ -114,5 +136,13 @@ module Shoryuken
114
136
 
115
137
  @running.make_false
116
138
  end
139
+
140
+ def fire_utilization_update_event
141
+ fire_event :utilization_update, false, {
142
+ group: @group,
143
+ max_processors: @max_processors,
144
+ busy_processors: busy
145
+ }
146
+ end
117
147
  end
118
148
  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