shoryuken 7.0.0.alpha2 → 7.0.0.rc1
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 +4 -4
- data/.github/workflows/specs.yml +22 -9
- data/Appraisals +6 -12
- data/CHANGELOG.md +14 -0
- data/bin/cli/sqs.rb +1 -1
- data/bin/shoryuken +1 -1
- data/gemfiles/rails_7_2.gemfile +10 -10
- data/gemfiles/rails_8_0.gemfile +10 -10
- data/gemfiles/rails_8_1.gemfile +19 -0
- data/lib/shoryuken/extensions/active_job_adapter.rb +13 -0
- data/lib/shoryuken/helpers/timer_task.rb +66 -0
- data/lib/shoryuken/launcher.rb +14 -0
- data/lib/shoryuken/logging/base.rb +26 -0
- data/lib/shoryuken/logging/pretty.rb +25 -0
- data/lib/shoryuken/logging/without_timestamp.rb +25 -0
- data/lib/shoryuken/logging.rb +3 -23
- data/lib/shoryuken/message.rb +114 -1
- data/lib/shoryuken/middleware/chain.rb +138 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +1 -1
- data/lib/shoryuken/runner.rb +3 -0
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker.rb +171 -0
- data/spec/integration/active_job_continuation_spec.rb +145 -0
- data/spec/shoryuken/extensions/active_job_continuation_spec.rb +110 -0
- data/spec/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/shoryuken/launcher_spec.rb +22 -0
- metadata +13 -3
- data/gemfiles/rails_7_0.gemfile +0 -19
- data/gemfiles/rails_7_1.gemfile +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44ec6add623605d696f0a4151aab96d37331250fa66a6b804edea13b06a2b816
|
|
4
|
+
data.tar.gz: efe2733bb86113a83b7788041262421c84ee4c50a47718b32ef8bdb505fa15fe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 84daafb107ff842e74cdb3872885b6c01ea34d95a5eaf6cf2efeeb3398bc835e570ea5b7f93b9451e4e5841d1d27b53ea35db677a4ddd78cdd75c13b417eebae
|
|
7
|
+
data.tar.gz: ad0dcb19240b8179c62bb6b74e23b1801d1f0d671a0f72a5b26d74a0bbd1f47158abd57a6cee091d88fe903ea91ce09afd34702f9086cb4b2cf14a9a17397509
|
data/.github/workflows/specs.yml
CHANGED
|
@@ -45,20 +45,17 @@ jobs:
|
|
|
45
45
|
name: Rails Specs
|
|
46
46
|
strategy:
|
|
47
47
|
matrix:
|
|
48
|
-
rails: ['7.
|
|
48
|
+
rails: ['7.2', '8.0', '8.1']
|
|
49
49
|
include:
|
|
50
|
-
- rails: '7.0'
|
|
51
|
-
ruby: '3.1'
|
|
52
|
-
gemfile: gemfiles/rails_7_0.gemfile
|
|
53
|
-
- rails: '7.1'
|
|
54
|
-
ruby: '3.2'
|
|
55
|
-
gemfile: gemfiles/rails_7_1.gemfile
|
|
56
50
|
- rails: '7.2'
|
|
57
|
-
ruby: '3.
|
|
51
|
+
ruby: '3.2'
|
|
58
52
|
gemfile: gemfiles/rails_7_2.gemfile
|
|
59
53
|
- rails: '8.0'
|
|
60
|
-
ruby: '3.
|
|
54
|
+
ruby: '3.3'
|
|
61
55
|
gemfile: gemfiles/rails_8_0.gemfile
|
|
56
|
+
- rails: '8.1'
|
|
57
|
+
ruby: '3.4'
|
|
58
|
+
gemfile: gemfiles/rails_8_1.gemfile
|
|
62
59
|
runs-on: ubuntu-latest
|
|
63
60
|
env:
|
|
64
61
|
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
|
@@ -73,3 +70,19 @@ jobs:
|
|
|
73
70
|
|
|
74
71
|
- name: Run Rails specs
|
|
75
72
|
run: bundle exec rake spec:rails
|
|
73
|
+
|
|
74
|
+
ci-success:
|
|
75
|
+
name: CI Success
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
if: always()
|
|
78
|
+
needs:
|
|
79
|
+
- all_specs
|
|
80
|
+
- rails_specs
|
|
81
|
+
steps:
|
|
82
|
+
- name: Check all jobs passed
|
|
83
|
+
if: |
|
|
84
|
+
contains(needs.*.result, 'failure') ||
|
|
85
|
+
contains(needs.*.result, 'cancelled') ||
|
|
86
|
+
contains(needs.*.result, 'skipped')
|
|
87
|
+
run: exit 1
|
|
88
|
+
- run: echo "All CI checks passed!"
|
data/Appraisals
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
appraise 'rails_7_0' do
|
|
2
|
-
group :test do
|
|
3
|
-
gem 'activejob', '~> 7.0'
|
|
4
|
-
end
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
appraise 'rails_7_1' do
|
|
8
|
-
group :test do
|
|
9
|
-
gem 'activejob', '~> 7.1'
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
1
|
appraise 'rails_7_2' do
|
|
14
2
|
group :test do
|
|
15
3
|
gem 'activejob', '~> 7.2'
|
|
@@ -21,3 +9,9 @@ appraise 'rails_8_0' do
|
|
|
21
9
|
gem 'activejob', '~> 8.0'
|
|
22
10
|
end
|
|
23
11
|
end
|
|
12
|
+
|
|
13
|
+
appraise 'rails_8_1' do
|
|
14
|
+
group :test do
|
|
15
|
+
gem 'activejob', '~> 8.1'
|
|
16
|
+
end
|
|
17
|
+
end
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
## [7.0.0] - Unreleased
|
|
2
|
+
- Enhancement: Add ActiveJob Continuations support (Rails 8.1+)
|
|
3
|
+
- Implements `stopping?` method in ActiveJob adapters to signal graceful shutdown
|
|
4
|
+
- Enables jobs to checkpoint progress and resume after interruption
|
|
5
|
+
- Handles past timestamps correctly (SQS treats negative delays as immediate delivery)
|
|
6
|
+
- Tracks shutdown state in Launcher via `stopping?` flag
|
|
7
|
+
- Leverages existing Shoryuken shutdown lifecycle (stop/stop! methods)
|
|
8
|
+
- Includes comprehensive integration tests with continuable jobs
|
|
9
|
+
- See Rails PR #55127 for more details on ActiveJob Continuations
|
|
10
|
+
|
|
11
|
+
- Breaking: Remove support for Rails versions older than 7.2
|
|
12
|
+
- Rails 7.0 and 7.1 have reached end-of-life and are no longer supported
|
|
13
|
+
- Supported versions: Rails 7.2, 8.0, and 8.1
|
|
14
|
+
- Users on older Rails versions should upgrade or remain on Shoryuken 6.x
|
|
15
|
+
|
|
2
16
|
- Enhancement: Replace Concurrent::AtomicFixnum with pure Ruby AtomicCounter
|
|
3
17
|
- Removes external dependency on concurrent-ruby for atomic fixnum operations
|
|
4
18
|
- Introduces Shoryuken::Helpers::AtomicCounter as a thread-safe alternative using Mutex
|
data/bin/cli/sqs.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Shoryuken
|
|
|
13
13
|
no_commands do
|
|
14
14
|
def normalize_dump_message(message)
|
|
15
15
|
# symbolize_keys is needed for keeping it compatible with `requeue`
|
|
16
|
-
attributes = message[:attributes].
|
|
16
|
+
attributes = message[:attributes].transform_keys(&:to_sym)
|
|
17
17
|
|
|
18
18
|
# See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html
|
|
19
19
|
# The `string_list_values` and `binary_list_values` are not implemented. Reserved for future use.
|
data/bin/shoryuken
CHANGED
|
@@ -29,7 +29,7 @@ module Shoryuken
|
|
|
29
29
|
method_option :delay, aliases: '-D', type: :numeric,
|
|
30
30
|
desc: 'Number of seconds to pause fetching from an empty queue'
|
|
31
31
|
def start
|
|
32
|
-
opts = options.to_h.
|
|
32
|
+
opts = options.to_h.transform_keys(&:to_sym)
|
|
33
33
|
|
|
34
34
|
say '[DEPRECATED] Please use --config instead of --config-file', :yellow if opts[:config_file]
|
|
35
35
|
|
data/gemfiles/rails_7_2.gemfile
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# This file was generated by Appraisal
|
|
2
2
|
|
|
3
|
-
source
|
|
3
|
+
source "https://rubygems.org"
|
|
4
4
|
|
|
5
5
|
group :test do
|
|
6
|
-
gem
|
|
7
|
-
gem
|
|
8
|
-
gem
|
|
9
|
-
gem
|
|
10
|
-
gem
|
|
6
|
+
gem "activejob", "~> 7.2"
|
|
7
|
+
gem "httparty"
|
|
8
|
+
gem "multi_xml"
|
|
9
|
+
gem "simplecov"
|
|
10
|
+
gem "warning"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
group :development do
|
|
14
|
-
gem
|
|
15
|
-
gem
|
|
16
|
-
gem
|
|
14
|
+
gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
|
|
15
|
+
gem "pry-byebug"
|
|
16
|
+
gem "rubocop"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
gemspec path:
|
|
19
|
+
gemspec path: "../"
|
data/gemfiles/rails_8_0.gemfile
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# This file was generated by Appraisal
|
|
2
2
|
|
|
3
|
-
source
|
|
3
|
+
source "https://rubygems.org"
|
|
4
4
|
|
|
5
5
|
group :test do
|
|
6
|
-
gem
|
|
7
|
-
gem
|
|
8
|
-
gem
|
|
9
|
-
gem
|
|
10
|
-
gem
|
|
6
|
+
gem "activejob", "~> 8.0"
|
|
7
|
+
gem "httparty"
|
|
8
|
+
gem "multi_xml"
|
|
9
|
+
gem "simplecov"
|
|
10
|
+
gem "warning"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
group :development do
|
|
14
|
-
gem
|
|
15
|
-
gem
|
|
16
|
-
gem
|
|
14
|
+
gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
|
|
15
|
+
gem "pry-byebug"
|
|
16
|
+
gem "rubocop"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
gemspec path:
|
|
19
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
group :test do
|
|
6
|
+
gem "activejob", "~> 8.1"
|
|
7
|
+
gem "httparty"
|
|
8
|
+
gem "multi_xml"
|
|
9
|
+
gem "simplecov"
|
|
10
|
+
gem "warning"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
group :development do
|
|
14
|
+
gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
|
|
15
|
+
gem "pry-byebug"
|
|
16
|
+
gem "rubocop"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
gemspec path: "../"
|
|
@@ -37,6 +37,19 @@ module ActiveJob
|
|
|
37
37
|
true
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Indicates whether Shoryuken is in the process of shutting down.
|
|
41
|
+
#
|
|
42
|
+
# This method is required for ActiveJob Continuations support (Rails 8.1+).
|
|
43
|
+
# When true, it signals to jobs that they should checkpoint their progress
|
|
44
|
+
# and gracefully interrupt execution to allow for resumption after restart.
|
|
45
|
+
#
|
|
46
|
+
# @return [Boolean] true if Shoryuken is shutting down, false otherwise
|
|
47
|
+
# @see https://github.com/rails/rails/pull/55127 Rails ActiveJob Continuations
|
|
48
|
+
def stopping?
|
|
49
|
+
launcher = Shoryuken::Runner.instance.launcher
|
|
50
|
+
launcher&.stopping? || false
|
|
51
|
+
end
|
|
52
|
+
|
|
40
53
|
def enqueue(job, options = {}) # :nodoc:
|
|
41
54
|
register_worker!(job)
|
|
42
55
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
module Helpers
|
|
5
|
+
# A thread-safe timer task implementation.
|
|
6
|
+
# Drop-in replacement for Concurrent::TimerTask without external dependencies.
|
|
7
|
+
class TimerTask
|
|
8
|
+
def initialize(execution_interval:, &task)
|
|
9
|
+
raise ArgumentError, 'A block must be provided' unless block_given?
|
|
10
|
+
|
|
11
|
+
@execution_interval = Float(execution_interval)
|
|
12
|
+
raise ArgumentError, 'execution_interval must be positive' if @execution_interval <= 0
|
|
13
|
+
|
|
14
|
+
@task = task
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
@thread = nil
|
|
17
|
+
@running = false
|
|
18
|
+
@killed = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Start the timer task execution
|
|
22
|
+
def execute
|
|
23
|
+
@mutex.synchronize do
|
|
24
|
+
return self if @running || @killed
|
|
25
|
+
|
|
26
|
+
@running = true
|
|
27
|
+
@thread = Thread.new { run_timer_loop }
|
|
28
|
+
end
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Stop and kill the timer task
|
|
33
|
+
def kill
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
return false if @killed
|
|
36
|
+
|
|
37
|
+
@killed = true
|
|
38
|
+
@running = false
|
|
39
|
+
|
|
40
|
+
@thread.kill if @thread&.alive?
|
|
41
|
+
end
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def run_timer_loop
|
|
48
|
+
until @killed
|
|
49
|
+
sleep(@execution_interval)
|
|
50
|
+
break if @killed
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
@task.call
|
|
54
|
+
rescue => e
|
|
55
|
+
# Log the error but continue running
|
|
56
|
+
# This matches the behavior of Concurrent::TimerTask
|
|
57
|
+
warn "TimerTask execution error: #{e.message}"
|
|
58
|
+
warn e.backtrace.join("\n") if e.backtrace
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
ensure
|
|
62
|
+
@mutex.synchronize { @running = false }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/shoryuken/launcher.rb
CHANGED
|
@@ -6,6 +6,18 @@ module Shoryuken
|
|
|
6
6
|
|
|
7
7
|
def initialize
|
|
8
8
|
@managers = create_managers
|
|
9
|
+
@stopping = false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Indicates whether the launcher is in the process of stopping.
|
|
13
|
+
#
|
|
14
|
+
# This flag is set to true when either {#stop} or {#stop!} is called,
|
|
15
|
+
# and is used by ActiveJob adapters to signal jobs that they should
|
|
16
|
+
# checkpoint and prepare for graceful shutdown.
|
|
17
|
+
#
|
|
18
|
+
# @return [Boolean] true if stopping, false otherwise
|
|
19
|
+
def stopping?
|
|
20
|
+
@stopping
|
|
9
21
|
end
|
|
10
22
|
|
|
11
23
|
def start
|
|
@@ -16,6 +28,7 @@ module Shoryuken
|
|
|
16
28
|
end
|
|
17
29
|
|
|
18
30
|
def stop!
|
|
31
|
+
@stopping = true
|
|
19
32
|
initiate_stop
|
|
20
33
|
|
|
21
34
|
# Don't await here so the timeout below is not delayed
|
|
@@ -28,6 +41,7 @@ module Shoryuken
|
|
|
28
41
|
end
|
|
29
42
|
|
|
30
43
|
def stop
|
|
44
|
+
@stopping = true
|
|
31
45
|
fire_event(:quiet, true)
|
|
32
46
|
|
|
33
47
|
initiate_stop
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
module Logging
|
|
5
|
+
# Base formatter class that provides common functionality for Shoryuken log formatters.
|
|
6
|
+
# Provides thread ID generation and context management.
|
|
7
|
+
class Base < ::Logger::Formatter
|
|
8
|
+
# Generates a thread ID for the current thread.
|
|
9
|
+
# Uses a combination of thread object_id and process ID to create a unique identifier.
|
|
10
|
+
#
|
|
11
|
+
# @return [String] A base36-encoded thread identifier
|
|
12
|
+
def tid
|
|
13
|
+
Thread.current['shoryuken_tid'] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns the current logging context as a formatted string.
|
|
17
|
+
# Context is set using {Shoryuken::Logging.with_context}.
|
|
18
|
+
#
|
|
19
|
+
# @return [String] Formatted context string or empty string if no context
|
|
20
|
+
def context
|
|
21
|
+
c = Thread.current[:shoryuken_context]
|
|
22
|
+
c ? " #{c}" : ''
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
module Logging
|
|
5
|
+
# A pretty log formatter that includes timestamps, process ID, thread ID,
|
|
6
|
+
# context information, and severity in a human-readable format.
|
|
7
|
+
#
|
|
8
|
+
# Output format: "TIMESTAMP PID TID-THREAD_ID CONTEXT SEVERITY: MESSAGE"
|
|
9
|
+
#
|
|
10
|
+
# @example Output
|
|
11
|
+
# 2023-08-15T10:30:45Z 12345 TID-abc123 MyWorker/queue1/msg-456 INFO: Processing message
|
|
12
|
+
class Pretty < Base
|
|
13
|
+
# Formats a log message with timestamp and full context information.
|
|
14
|
+
#
|
|
15
|
+
# @param severity [String] Log severity level (DEBUG, INFO, WARN, ERROR, FATAL)
|
|
16
|
+
# @param time [Time] Timestamp when the log entry was created
|
|
17
|
+
# @param _program_name [String] Program name (unused)
|
|
18
|
+
# @param message [String] The log message
|
|
19
|
+
# @return [String] Formatted log entry with newline
|
|
20
|
+
def call(severity, time, _program_name, message)
|
|
21
|
+
"#{time.utc.iso8601} #{Process.pid} TID-#{tid}#{context} #{severity}: #{message}\n"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoryuken
|
|
4
|
+
module Logging
|
|
5
|
+
# A log formatter that excludes timestamps from output.
|
|
6
|
+
# Useful for environments where timestamps are added by external logging systems.
|
|
7
|
+
#
|
|
8
|
+
# Output format: "pid=PID tid=THREAD_ID CONTEXT SEVERITY: MESSAGE"
|
|
9
|
+
#
|
|
10
|
+
# @example Output
|
|
11
|
+
# pid=12345 tid=abc123 MyWorker/queue1/msg-456 INFO: Processing message
|
|
12
|
+
class WithoutTimestamp < Base
|
|
13
|
+
# Formats a log message without timestamp information.
|
|
14
|
+
#
|
|
15
|
+
# @param severity [String] Log severity level (DEBUG, INFO, WARN, ERROR, FATAL)
|
|
16
|
+
# @param _time [Time] Timestamp (unused)
|
|
17
|
+
# @param _program_name [String] Program name (unused)
|
|
18
|
+
# @param message [String] The log message
|
|
19
|
+
# @return [String] Formatted log entry with newline
|
|
20
|
+
def call(severity, _time, _program_name, message)
|
|
21
|
+
"pid=#{Process.pid} tid=#{tid}#{context} #{severity}: #{message}\n"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/shoryuken/logging.rb
CHANGED
|
@@ -2,32 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require 'time'
|
|
4
4
|
require 'logger'
|
|
5
|
+
require_relative 'logging/base'
|
|
6
|
+
require_relative 'logging/pretty'
|
|
7
|
+
require_relative 'logging/without_timestamp'
|
|
5
8
|
|
|
6
9
|
module Shoryuken
|
|
7
10
|
module Logging
|
|
8
|
-
class Base < ::Logger::Formatter
|
|
9
|
-
def tid
|
|
10
|
-
Thread.current['shoryuken_tid'] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def context
|
|
14
|
-
c = Thread.current[:shoryuken_context]
|
|
15
|
-
c ? " #{c}" : ''
|
|
16
|
-
end
|
|
17
|
-
end
|
|
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
11
|
|
|
32
12
|
def self.with_context(msg)
|
|
33
13
|
Thread.current[:shoryuken_context] = msg
|
data/lib/shoryuken/message.rb
CHANGED
|
@@ -1,9 +1,70 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Shoryuken
|
|
4
|
+
# Represents an SQS message received by a Shoryuken worker.
|
|
5
|
+
# This class wraps the raw AWS SQS message data and provides convenient methods
|
|
6
|
+
# for interacting with the message, including deletion and visibility timeout management.
|
|
7
|
+
#
|
|
8
|
+
# Message instances are automatically created by Shoryuken and passed to your
|
|
9
|
+
# worker's `perform` method as the first argument.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic worker with message handling
|
|
12
|
+
# class MyWorker
|
|
13
|
+
# include Shoryuken::Worker
|
|
14
|
+
# shoryuken_options queue: 'my_queue'
|
|
15
|
+
#
|
|
16
|
+
# def perform(sqs_msg, body)
|
|
17
|
+
# puts "Processing message #{sqs_msg.message_id}"
|
|
18
|
+
# puts "Message body: #{body}"
|
|
19
|
+
# puts "Queue: #{sqs_msg.queue_name}"
|
|
20
|
+
#
|
|
21
|
+
# # Process the message...
|
|
22
|
+
#
|
|
23
|
+
# # Delete the message when done (if auto_delete is false)
|
|
24
|
+
# sqs_msg.delete unless auto_delete?
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# @example Working with message attributes
|
|
29
|
+
# def perform(sqs_msg, body)
|
|
30
|
+
# # Access standard SQS attributes
|
|
31
|
+
# sender_id = sqs_msg.attributes['SenderId']
|
|
32
|
+
# sent_timestamp = sqs_msg.attributes['SentTimestamp']
|
|
33
|
+
#
|
|
34
|
+
# # Access custom message attributes
|
|
35
|
+
# priority = sqs_msg.message_attributes['Priority']&.[]('StringValue')
|
|
36
|
+
# user_id = sqs_msg.message_attributes['UserId']&.[]('StringValue')
|
|
37
|
+
# end
|
|
4
38
|
class Message
|
|
5
39
|
extend Forwardable
|
|
6
40
|
|
|
41
|
+
# @!method message_id
|
|
42
|
+
# Returns the unique SQS message ID.
|
|
43
|
+
# @return [String] The message ID assigned by SQS
|
|
44
|
+
#
|
|
45
|
+
# @!method receipt_handle
|
|
46
|
+
# Returns the receipt handle needed for deleting or modifying the message.
|
|
47
|
+
# @return [String] The receipt handle for this message
|
|
48
|
+
#
|
|
49
|
+
# @!method md5_of_body
|
|
50
|
+
# Returns the MD5 hash of the message body.
|
|
51
|
+
# @return [String] MD5 hash of the message body
|
|
52
|
+
#
|
|
53
|
+
# @!method body
|
|
54
|
+
# Returns the raw message body as received from SQS.
|
|
55
|
+
# @return [String] The raw message body
|
|
56
|
+
#
|
|
57
|
+
# @!method attributes
|
|
58
|
+
# Returns the SQS message attributes (system attributes).
|
|
59
|
+
# @return [Hash] System attributes like SenderId, SentTimestamp, etc.
|
|
60
|
+
#
|
|
61
|
+
# @!method md5_of_message_attributes
|
|
62
|
+
# Returns the MD5 hash of the message attributes.
|
|
63
|
+
# @return [String] MD5 hash of message attributes
|
|
64
|
+
#
|
|
65
|
+
# @!method message_attributes
|
|
66
|
+
# Returns custom message attributes set by the sender.
|
|
67
|
+
# @return [Hash] Custom message attributes with typed values
|
|
7
68
|
def_delegators(:data,
|
|
8
69
|
:message_id,
|
|
9
70
|
:receipt_handle,
|
|
@@ -13,8 +74,24 @@ module Shoryuken
|
|
|
13
74
|
:md5_of_message_attributes,
|
|
14
75
|
:message_attributes)
|
|
15
76
|
|
|
16
|
-
|
|
77
|
+
# @return [Aws::SQS::Client] The SQS client used for message operations
|
|
78
|
+
attr_accessor :client
|
|
17
79
|
|
|
80
|
+
# @return [String] The URL of the SQS queue this message came from
|
|
81
|
+
attr_accessor :queue_url
|
|
82
|
+
|
|
83
|
+
# @return [String] The name of the queue this message came from
|
|
84
|
+
attr_accessor :queue_name
|
|
85
|
+
|
|
86
|
+
# @return [Aws::SQS::Types::Message] The raw SQS message data
|
|
87
|
+
attr_accessor :data
|
|
88
|
+
|
|
89
|
+
# Creates a new Message instance wrapping SQS message data.
|
|
90
|
+
#
|
|
91
|
+
# @param client [Aws::SQS::Client] The SQS client for message operations
|
|
92
|
+
# @param queue [Shoryuken::Queue] The queue this message came from
|
|
93
|
+
# @param data [Aws::SQS::Types::Message] The raw SQS message data
|
|
94
|
+
# @api private
|
|
18
95
|
def initialize(client, queue, data)
|
|
19
96
|
self.client = client
|
|
20
97
|
self.data = data
|
|
@@ -22,6 +99,12 @@ module Shoryuken
|
|
|
22
99
|
self.queue_name = queue.name
|
|
23
100
|
end
|
|
24
101
|
|
|
102
|
+
# Deletes this message from the SQS queue.
|
|
103
|
+
# Once deleted, the message will not be redelivered and cannot be retrieved again.
|
|
104
|
+
# This is typically called after successful message processing when auto_delete is disabled.
|
|
105
|
+
#
|
|
106
|
+
# @return [Aws::SQS::Types::DeleteMessageResult] The deletion result
|
|
107
|
+
# @raise [Aws::SQS::Errors::ServiceError] If the deletion fails
|
|
25
108
|
def delete
|
|
26
109
|
client.delete_message(
|
|
27
110
|
queue_url: queue_url,
|
|
@@ -29,12 +112,42 @@ module Shoryuken
|
|
|
29
112
|
)
|
|
30
113
|
end
|
|
31
114
|
|
|
115
|
+
# Changes the visibility timeout of this message with additional options.
|
|
116
|
+
# This allows you to hide the message from other consumers for a longer or shorter period.
|
|
117
|
+
#
|
|
118
|
+
# @param options [Hash] Options to pass to change_message_visibility
|
|
119
|
+
# @option options [Integer] :visibility_timeout New visibility timeout in seconds
|
|
120
|
+
# @return [Aws::SQS::Types::ChangeMessageVisibilityResult] The change result
|
|
121
|
+
# @raise [Aws::SQS::Errors::ServiceError] If the change fails
|
|
122
|
+
#
|
|
123
|
+
# @example Extending visibility with additional options
|
|
124
|
+
# sqs_msg.change_visibility(visibility_timeout: 300)
|
|
125
|
+
#
|
|
126
|
+
# @see #visibility_timeout= For a simpler interface
|
|
32
127
|
def change_visibility(options)
|
|
33
128
|
client.change_message_visibility(
|
|
34
129
|
options.merge(queue_url: queue_url, receipt_handle: data.receipt_handle)
|
|
35
130
|
)
|
|
36
131
|
end
|
|
37
132
|
|
|
133
|
+
# Sets the visibility timeout for this message.
|
|
134
|
+
# This is a convenience method for changing only the visibility timeout.
|
|
135
|
+
#
|
|
136
|
+
# @param timeout [Integer] New visibility timeout in seconds (0-43200)
|
|
137
|
+
# @return [Aws::SQS::Types::ChangeMessageVisibilityResult] The change result
|
|
138
|
+
# @raise [Aws::SQS::Errors::ServiceError] If the change fails
|
|
139
|
+
#
|
|
140
|
+
# @example Extending processing time
|
|
141
|
+
# def perform(sqs_msg, body)
|
|
142
|
+
# if complex_processing_needed?(body)
|
|
143
|
+
# sqs_msg.visibility_timeout = 1800 # 30 minutes
|
|
144
|
+
# end
|
|
145
|
+
#
|
|
146
|
+
# process_message(body)
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# @example Making message immediately visible again
|
|
150
|
+
# sqs_msg.visibility_timeout = 0 # Make visible immediately
|
|
38
151
|
def visibility_timeout=(timeout)
|
|
39
152
|
client.change_message_visibility(
|
|
40
153
|
queue_url: queue_url,
|