toiler 0.6.1 → 0.7.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: 0be5aeacdc4416fc5d665387f5e5e8858c81b99f00cf63e58c71753f706f1d5d
4
- data.tar.gz: 4a2d016c5b7a44cecce56076c02e058cb37952620be0d5a55b59fba8291f2316
3
+ metadata.gz: 6565dc34ea694f3407a7bf58108134a277197f1e145990749e0de7cb53f9d02f
4
+ data.tar.gz: 13f9b2aa0c289b4e66098e942c15ef79e402db53ccd67fd8b06bafba0c292377
5
5
  SHA512:
6
- metadata.gz: 52222e17b30a37eb7a136a9114d44b263be27a16e085cbd35f0cff7a56618e206fc92da937fa20dce37cd23e5447f7c3a2b997197ce5b9c64acc4a4baecf48c0
7
- data.tar.gz: 30d5b088632efa45bb91f4c885efdbe71a3f828b21230fa0cce03e29295fd0941569f9ec54f5af37ffeb2eebaae08a42b7d5a10e797e86b8129ec920c74cc4e7
6
+ metadata.gz: 34ca2858e5a70f8cf0187a93f3d32d98b28153d4b184aa89d42534ca6aeed5cabd514b4e7b92d24fe88900f623f23fc39ad6e991f0852676c1f250a60add0032
7
+ data.tar.gz: 8c719d64993aa04ea0e3e3f9e794c9c97a1925c776187f87f398f30df7ae266ef2235b0e103560c4cacbdb32223cc923175299f7f50fc4d27c670216dad9e294
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ # Documentation:
2
+ # Enabled: false
3
+ #
4
+ # Style/ClassAndModuleChildren:
5
+ # Enabled: false
6
+
7
+ Layout/LineLength:
8
+ Max: 120
9
+
10
+ Metrics/MethodLength:
11
+ Max: 20
12
+
13
+ Metrics/AbcSize:
14
+ Max: 40
15
+
16
+ Metrics/ClassLength:
17
+ CountComments: false
18
+ Max: 200
19
+
20
+ AllCops:
21
+ Exclude:
22
+ - 'vendor/**/*'
23
+ - 'tmp/**/*'
24
+ - 'config/**/*'
25
+ - 'bin/**'
26
+ - 'db/**/*'
27
+ - 'spec/**/*'
28
+ NewCops: enable
29
+ TargetRubyVersion: 2.6
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.2.2
1
+ 2.6.8
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/Gemfile.lock CHANGED
@@ -1,60 +1,148 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- toiler (0.5.1.pre7)
4
+ toiler (0.7.0)
5
5
  aws-sdk-sqs (>= 1.0.0, < 2.0.0)
6
6
  concurrent-ruby (~> 1.0, >= 1.0.0)
7
7
  concurrent-ruby-edge (~> 0.3, >= 0.3)
8
+ google-cloud-pubsub (~> 2.9, >= 2.9.1)
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
12
- ast (2.4.0)
13
- aws-eventstream (1.0.1)
14
- aws-partitions (1.105.0)
15
- aws-sdk-core (3.31.0)
16
- aws-eventstream (~> 1.0)
17
- aws-partitions (~> 1.0)
18
- aws-sigv4 (~> 1.0)
13
+ addressable (2.8.0)
14
+ public_suffix (>= 2.0.2, < 5.0)
15
+ ast (2.4.2)
16
+ aws-eventstream (1.2.0)
17
+ aws-partitions (1.573.0)
18
+ aws-sdk-core (3.130.0)
19
+ aws-eventstream (~> 1, >= 1.0.2)
20
+ aws-partitions (~> 1, >= 1.525.0)
21
+ aws-sigv4 (~> 1.1)
19
22
  jmespath (~> 1.0)
20
- aws-sdk-sqs (1.7.0)
21
- aws-sdk-core (~> 3, >= 3.26.0)
22
- aws-sigv4 (~> 1.0)
23
- aws-sigv4 (1.0.3)
24
- concurrent-ruby (1.0.5)
25
- concurrent-ruby-edge (0.3.1)
26
- concurrent-ruby (= 1.0.5)
27
- diff-lcs (1.3)
28
- jaro_winkler (1.5.1)
29
- jmespath (1.4.0)
30
- parallel (1.12.1)
31
- parser (2.5.1.2)
32
- ast (~> 2.4.0)
33
- powerpack (0.1.2)
34
- rainbow (3.0.0)
35
- rspec (3.8.0)
36
- rspec-core (~> 3.8.0)
37
- rspec-expectations (~> 3.8.0)
38
- rspec-mocks (~> 3.8.0)
39
- rspec-core (3.8.0)
40
- rspec-support (~> 3.8.0)
41
- rspec-expectations (3.8.1)
23
+ aws-sdk-sqs (1.51.0)
24
+ aws-sdk-core (~> 3, >= 3.127.0)
25
+ aws-sigv4 (~> 1.1)
26
+ aws-sigv4 (1.4.0)
27
+ aws-eventstream (~> 1, >= 1.0.2)
28
+ concurrent-ruby (1.1.10)
29
+ concurrent-ruby-edge (0.6.0)
30
+ concurrent-ruby (~> 1.1.6)
31
+ diff-lcs (1.5.0)
32
+ faraday (1.10.0)
33
+ faraday-em_http (~> 1.0)
34
+ faraday-em_synchrony (~> 1.0)
35
+ faraday-excon (~> 1.1)
36
+ faraday-httpclient (~> 1.0)
37
+ faraday-multipart (~> 1.0)
38
+ faraday-net_http (~> 1.0)
39
+ faraday-net_http_persistent (~> 1.0)
40
+ faraday-patron (~> 1.0)
41
+ faraday-rack (~> 1.0)
42
+ faraday-retry (~> 1.0)
43
+ ruby2_keywords (>= 0.0.4)
44
+ faraday-em_http (1.0.0)
45
+ faraday-em_synchrony (1.0.0)
46
+ faraday-excon (1.1.0)
47
+ faraday-httpclient (1.0.1)
48
+ faraday-multipart (1.0.3)
49
+ multipart-post (>= 1.2, < 3)
50
+ faraday-net_http (1.0.1)
51
+ faraday-net_http_persistent (1.2.0)
52
+ faraday-patron (1.0.0)
53
+ faraday-rack (1.0.0)
54
+ faraday-retry (1.0.3)
55
+ gapic-common (0.8.0)
56
+ faraday (~> 1.3)
57
+ google-protobuf (~> 3.14)
58
+ googleapis-common-protos (>= 1.3.11, < 2.a)
59
+ googleapis-common-protos-types (>= 1.0.6, < 2.a)
60
+ googleauth (>= 0.17.0, < 2.a)
61
+ grpc (~> 1.36)
62
+ google-cloud-core (1.6.0)
63
+ google-cloud-env (~> 1.0)
64
+ google-cloud-errors (~> 1.0)
65
+ google-cloud-env (1.6.0)
66
+ faraday (>= 0.17.3, < 3.0)
67
+ google-cloud-errors (1.2.0)
68
+ google-cloud-pubsub (2.9.1)
69
+ concurrent-ruby (~> 1.1)
70
+ google-cloud-core (~> 1.5)
71
+ google-cloud-pubsub-v1 (~> 0.0)
72
+ google-cloud-pubsub-v1 (0.8.0)
73
+ gapic-common (>= 0.7, < 2.a)
74
+ google-cloud-errors (~> 1.0)
75
+ grpc-google-iam-v1 (>= 0.6.10, < 2.a)
76
+ google-protobuf (3.20.0)
77
+ google-protobuf (3.20.0-x64-mingw32)
78
+ googleapis-common-protos (1.3.12)
79
+ google-protobuf (~> 3.14)
80
+ googleapis-common-protos-types (~> 1.2)
81
+ grpc (~> 1.27)
82
+ googleapis-common-protos-types (1.3.0)
83
+ google-protobuf (~> 3.14)
84
+ googleauth (1.1.2)
85
+ faraday (>= 0.17.3, < 3.a)
86
+ jwt (>= 1.4, < 3.0)
87
+ memoist (~> 0.16)
88
+ multi_json (~> 1.11)
89
+ os (>= 0.9, < 2.0)
90
+ signet (>= 0.16, < 2.a)
91
+ grpc (1.45.0)
92
+ google-protobuf (~> 3.19)
93
+ googleapis-common-protos-types (~> 1.0)
94
+ grpc (1.45.0-x64-mingw32)
95
+ google-protobuf (~> 3.19)
96
+ googleapis-common-protos-types (~> 1.0)
97
+ grpc-google-iam-v1 (1.0.0)
98
+ google-protobuf (~> 3.14)
99
+ googleapis-common-protos (>= 1.3.12, < 2.0)
100
+ grpc (~> 1.27)
101
+ jmespath (1.6.1)
102
+ jwt (2.3.0)
103
+ memoist (0.16.2)
104
+ multi_json (1.15.0)
105
+ multipart-post (2.1.1)
106
+ os (1.1.4)
107
+ parallel (1.22.1)
108
+ parser (3.1.1.0)
109
+ ast (~> 2.4.1)
110
+ public_suffix (4.0.6)
111
+ rainbow (3.1.1)
112
+ regexp_parser (2.2.1)
113
+ rexml (3.2.5)
114
+ rspec (3.11.0)
115
+ rspec-core (~> 3.11.0)
116
+ rspec-expectations (~> 3.11.0)
117
+ rspec-mocks (~> 3.11.0)
118
+ rspec-core (3.11.0)
119
+ rspec-support (~> 3.11.0)
120
+ rspec-expectations (3.11.0)
42
121
  diff-lcs (>= 1.2.0, < 2.0)
43
- rspec-support (~> 3.8.0)
44
- rspec-mocks (3.8.0)
122
+ rspec-support (~> 3.11.0)
123
+ rspec-mocks (3.11.1)
45
124
  diff-lcs (>= 1.2.0, < 2.0)
46
- rspec-support (~> 3.8.0)
47
- rspec-support (3.8.0)
48
- rubocop (0.58.2)
49
- jaro_winkler (~> 1.5.1)
125
+ rspec-support (~> 3.11.0)
126
+ rspec-support (3.11.0)
127
+ rubocop (1.26.1)
50
128
  parallel (~> 1.10)
51
- parser (>= 2.5, != 2.5.1.1)
52
- powerpack (~> 0.1)
129
+ parser (>= 3.1.0.0)
53
130
  rainbow (>= 2.2.2, < 4.0)
131
+ regexp_parser (>= 1.8, < 3.0)
132
+ rexml
133
+ rubocop-ast (>= 1.16.0, < 2.0)
54
134
  ruby-progressbar (~> 1.7)
55
- unicode-display_width (~> 1.0, >= 1.0.1)
56
- ruby-progressbar (1.10.0)
57
- unicode-display_width (1.4.0)
135
+ unicode-display_width (>= 1.4.0, < 3.0)
136
+ rubocop-ast (1.16.0)
137
+ parser (>= 3.1.1.0)
138
+ ruby-progressbar (1.11.0)
139
+ ruby2_keywords (0.0.5)
140
+ signet (0.16.1)
141
+ addressable (~> 2.8)
142
+ faraday (>= 0.17.5, < 3.0)
143
+ jwt (>= 1.5, < 3.0)
144
+ multi_json (~> 1.10)
145
+ unicode-display_width (2.1.0)
58
146
 
59
147
  PLATFORMS
60
148
  ruby
@@ -66,4 +154,4 @@ DEPENDENCIES
66
154
  toiler!
67
155
 
68
156
  BUNDLED WITH
69
- 1.16.3
157
+ 1.17.2
data/README.md CHANGED
@@ -14,21 +14,17 @@ Instead of [shoryuken's](https://github.com/phstc/shoryuken) loadbalancing appr
14
14
  ### Long-Polling
15
15
 
16
16
  A Fetcher thread is spawned for each queue.
17
- Fetchers are resposible for polling SQS and retreiving messages.
17
+ Fetchers are resposible for polling SQS/PubSub and retreiving messages.
18
18
  They are optimised to not bring more messages than the amount of processors avaiable for such queue.
19
19
  By long-polling fetchers wait for a configurable amount of time for messages to become available on a single request, this prevents unneccesarilly requesting messages when there are none.
20
20
 
21
21
  ### Message Parsing
22
22
 
23
- Workers can configure a parser Class or Proc to parse an SQS message body before being processed.
23
+ Workers can configure a parser Class or Proc to parse a message body before being processed.
24
24
 
25
- ### Batches
25
+ ### Deadline Extension
26
26
 
27
- Toiler allows a Worker to be able to receive a batch of messages instead of a single one.
28
-
29
- ### Auto Visibility Extension
30
-
31
- Toiler has the ability to automatically extend the visibility timeout of and SQS message to prevent the message from re-entering the queue if processing of such message is taking longer than the queue's visibility timeout.
27
+ Toiler has the ability to automatically extend the ack deadline of and messages to prevent the message from re-entering the queue if processing of such message is taking longer than the queue's ack deadline or visibility timeout.
32
28
 
33
29
  ## Instalation
34
30
 
@@ -59,8 +55,9 @@ class MyWorker
59
55
 
60
56
  # toiler_options parser: ->(sqs_msg){ REXML::Document.new(sqs_msg.body) }
61
57
  # toiler_options parser: MultiJson
62
- # toiler_options auto_visibility_timeout: true
58
+ # toiler_options deadline_extension: true
63
59
  # toiler_options batch: true
60
+ # toiler_options queue: 'subscription', concurrency: 5, auto_delete: true, provider: :gcp
64
61
 
65
62
  #Example connection client that should be shared across all instances of MyWorker
66
63
  @@client = ConnectionClient.new
@@ -82,10 +79,13 @@ end
82
79
 
83
80
  ```yaml
84
81
  aws:
85
- access_key_id: ... # or <%= ENV['AWS_ACCESS_KEY_ID'] %>
86
- secret_access_key: ... # or <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
87
- region: us-east-1 # or <%= ENV['AWS_REGION'] %>
88
- wait: 20 # The time in seconds to wait for messages during long-polling
82
+ access_key_id: ... # or <%= ENV['AWS_ACCESS_KEY_ID'] %>
83
+ secret_access_key: ... # or <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
84
+ region: us-east-1 # or <%= ENV['AWS_REGION'] %>
85
+ gcp:
86
+ project_id: my-project # or <%= ENV['GCP_PROJECT'] %>
87
+ credentials: /path/to/keyfile.json # or <%= ENV['GCP_CREDENTIALS'] %>
88
+ wait: 20 # The time in seconds to wait for messages during long-polling
89
89
  ```
90
90
 
91
91
  ### Rails Integration
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
@@ -1,29 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'toiler/actor/utils/actor_logging'
2
4
  require 'toiler/aws/queue'
5
+ require 'toiler/gcp/queue'
3
6
 
4
7
  module Toiler
5
8
  module Actor
6
- # Actor polling for messages only when processors are ready, otherwise idle
9
+ # Actor pulling messages only when processors are ready, otherwise idle
7
10
  class Fetcher < Concurrent::Actor::RestartingContext
8
11
  include Utils::ActorLogging
9
12
 
10
- FETCH_LIMIT = 10
13
+ attr_reader :queue, :wait, :ack_deadline, :free_processors,
14
+ :executing, :waiting_messages, :concurrency,
15
+ :scheduled_task
11
16
 
12
- attr_accessor :queue, :wait, :visibility_timeout, :free_processors,
13
- :executing, :waiting_messages, :concurrency
17
+ def initialize(queue_name, count, provider)
18
+ super()
14
19
 
15
- def initialize(queue, client, count)
16
- debug "Initializing Fetcher for queue #{queue}..."
17
- @queue = Toiler::Aws::Queue.new queue, client
20
+ debug "Initializing Fetcher for queue #{queue_name} and provider #{provider}..."
18
21
  @wait = Toiler.options[:wait] || 60
19
22
  @free_processors = count
20
- @batch = Toiler.worker_class_registry[queue].batch?
21
- @visibility_timeout = @queue.visibility_timeout
22
23
  @executing = false
23
24
  @waiting_messages = 0
24
25
  @concurrency = count
25
- debug "Finished initializing Fetcher for queue #{queue}"
26
- tell :poll_messages
26
+ @scheduled_task = nil
27
+ init_queue(queue_name, provider)
28
+ debug "Finished initializing Fetcher for queue #{queue_name} and provider #{provider}..."
29
+ tell :pull_messages
27
30
  end
28
31
 
29
32
  def default_executor
@@ -35,8 +38,8 @@ module Toiler
35
38
  method, *args = msg
36
39
  send(method, *args)
37
40
  rescue StandardError, SystemStackError => e
38
- # rescue SystemStackError, if we misbehave and cause a stack level too deep exception, we should be able to recover
39
- error "Fetcher #{queue.name} raised exception #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
41
+ # if we misbehave and cause a stack level too deep exception, we should be able to recover
42
+ error "Fetcher #{@queue.name} raised exception #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
40
43
  ensure
41
44
  @executing = false
42
45
  end
@@ -47,26 +50,26 @@ module Toiler
47
50
 
48
51
  private
49
52
 
50
- def batch?
51
- @batch
53
+ def init_queue(queue_name, provider)
54
+ if provider.nil? || provider.to_sym == :aws
55
+ @queue = Toiler::Aws::Queue.new queue_name, Toiler.aws_client
56
+ elsif provider.to_sym == :gcp
57
+ @queue = Toiler::Gcp::Queue.new queue_name, Toiler.gcp_client
58
+ else
59
+ raise StandardError, "unknown provider #{provider}"
60
+ end
61
+ @ack_deadline = @queue.ack_deadline
52
62
  end
53
63
 
54
64
  def processor_finished
55
- debug "Fetcher #{queue.name} received processor finished signal..."
65
+ debug "Fetcher #{@queue.name} received processor finished signal..."
56
66
  @free_processors += 1
57
- tell :poll_messages
58
- end
59
-
60
- def max_messages
61
- batch? ? FETCH_LIMIT : [FETCH_LIMIT, free_processors].min
67
+ tell :pull_messages
62
68
  end
63
69
 
64
- def poll_future(max_number_of_messages)
70
+ def pull_future(max_number_of_messages)
65
71
  Concurrent::Promises.future do
66
- queue.receive_messages attribute_names: %w[All],
67
- message_attribute_names: %w[All],
68
- wait_time_seconds: wait,
69
- max_number_of_messages: max_number_of_messages
72
+ @queue.receive_messages wait: @wait, max_messages: max_number_of_messages
70
73
  end
71
74
  end
72
75
 
@@ -74,49 +77,81 @@ module Toiler
74
77
  @waiting_messages -= messages
75
78
  end
76
79
 
77
- def poll_messages
78
- return unless should_poll?
80
+ def max_messages
81
+ # limit max messages to 10% of concurrency to always ensure we have
82
+ # 10 concurrent fetches and improved latency
83
+ [@queue.max_messages, (@concurrency * 0.1).ceil].min
84
+ end
85
+
86
+ def needed_messages
87
+ @free_processors - @waiting_messages
88
+ end
89
+
90
+ def pull_messages
91
+ if needed_messages < max_messages
92
+ # a pull is already scheduled and we dont fit a full batch, return
93
+ return unless @scheduled_task.nil?
94
+
95
+ free_percent = free_processors.to_f / concurrency
96
+ # wait time linear to the amount of free workers with a maximum of 5 seconds,
97
+ # when there are more free workers, we can theoretically wait more time, since
98
+ # we already have workers waiting for messages.
99
+ wait_time = 0.1 + (5 * free_percent)
100
+
101
+ # schedule a message pull if we cannot fill a batch
102
+ # this ensures we wait some time for more messages to arrive
103
+ @scheduled_task = Concurrent::ScheduledTask.execute(wait_time) do
104
+ tell [:do_pull_messages, true]
105
+ end
106
+ end
107
+
108
+ # we can fit a whole batch, if there was already a scheduled task
109
+ # we just let it run, it will only pull messages if there are more
110
+ # needed messages
111
+ do_pull_messages false
112
+ end
113
+
114
+ def do_pull_messages(clear_scheduled_task)
115
+ @scheduled_task = nil if clear_scheduled_task
79
116
 
80
- max_number_of_messages = max_messages
81
- return if waiting_messages > 0 && !full_batch?(max_number_of_messages)
117
+ return unless should_pull?
82
118
 
83
- @waiting_messages += max_number_of_messages
119
+ current_needed_messages = needed_messages
84
120
 
85
- debug "Fetcher #{queue.name} polling messages..."
86
- future = poll_future max_number_of_messages
121
+ current_needed_messages = max_messages if current_needed_messages >= max_messages
122
+
123
+ @waiting_messages += current_needed_messages
124
+
125
+ debug "Fetcher #{@queue.name} pulling messages..."
126
+ future = pull_future current_needed_messages
87
127
  future.on_rejection! do
88
- tell [:release_messages, max_number_of_messages]
89
- tell :poll_messages
128
+ tell [:release_messages, current_needed_messages]
129
+ tell :pull_messages
90
130
  end
91
131
  future.on_fulfillment! do |msgs|
92
132
  tell [:assign_messages, msgs] if !msgs.nil? && !msgs.empty?
93
- tell [:release_messages, max_number_of_messages]
94
- tell :poll_messages
133
+ tell [:release_messages, current_needed_messages]
134
+ tell :pull_messages
95
135
  end
96
136
 
97
137
  # defer method execution to avoid recursion
98
- tell :poll_messages if should_poll?
99
- end
100
-
101
- def should_poll?
102
- free_processors / 2 > waiting_messages
138
+ tell :pull_messages if should_pull?
103
139
  end
104
140
 
105
- def full_batch?(max_number_of_messages)
106
- max_number_of_messages == FETCH_LIMIT || max_number_of_messages >= concurrency * 0.1
141
+ def should_pull?
142
+ needed_messages.positive?
107
143
  end
108
144
 
109
145
  def processor_pool
110
- @processor_pool ||= Toiler.processor_pool queue.name
146
+ @processor_pool ||= Toiler.processor_pool @queue.name
111
147
  end
112
148
 
113
149
  def assign_messages(messages)
114
- messages = [messages] if batch?
115
150
  messages.each do |m|
116
- processor_pool.tell [:process, visibility_timeout, m]
151
+ processor_pool.tell [:process, @ack_deadline, m]
117
152
  @free_processors -= 1
118
153
  end
119
- debug "Fetcher #{queue.name} assigned #{messages.count} messages"
154
+ debug "Fetcher #{@queue.name} assigned #{messages.count} messages"
120
155
  end
121
156
  end
122
157
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'toiler/actor/utils/actor_logging'
3
5
 
@@ -7,10 +9,12 @@ module Toiler
7
9
  class Processor < Concurrent::Actor::RestartingContext
8
10
  include Utils::ActorLogging
9
11
 
10
- attr_accessor :queue, :worker_class, :fetcher, :body_parser,
11
- :extend_callback, :executing, :thread
12
+ attr_reader :queue, :worker_class, :body_parser,
13
+ :executing, :thread
12
14
 
13
15
  def initialize(queue)
16
+ super()
17
+
14
18
  @queue = queue
15
19
  @worker_class = Toiler.worker_class_registry[queue]
16
20
  @executing = false
@@ -23,15 +27,15 @@ module Toiler
23
27
  end
24
28
 
25
29
  def fetcher
26
- @fetcher ||= Toiler.fetcher queue
30
+ @fetcher ||= Toiler.fetcher @queue
27
31
  end
28
32
 
29
33
  def on_message(msg)
30
34
  method, *args = msg
31
35
  send(method, *args)
32
36
  rescue StandardError, SystemStackError => e
33
- # rescue SystemStackError, if clients misbehave and cause a stack level too deep exception, we should be able to recover
34
- error "Processor #{queue} failed processing, reason: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
37
+ # if clients misbehave and cause a stack level too deep exception, we should be able to recover
38
+ error "Processor #{@queue} failed processing, reason: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
35
39
  end
36
40
 
37
41
  def executing?
@@ -41,31 +45,30 @@ module Toiler
41
45
  private
42
46
 
43
47
  def init_options
44
- @auto_visibility_timeout = @worker_class.auto_visibility_timeout?
48
+ @deadline_extension = @worker_class.auto_visibility_timeout? || @worker_class.deadline_extension?
45
49
  @auto_delete = @worker_class.auto_delete?
46
50
  toiler_options = @worker_class.toiler_options
47
51
  @body_parser = toiler_options[:parser]
48
- @extend_callback = toiler_options[:on_visibility_extend]
49
52
  end
50
53
 
51
- def auto_visibility_timeout?
52
- @auto_visibility_timeout
54
+ def deadline_extension?
55
+ @deadline_extension
53
56
  end
54
57
 
55
58
  def auto_delete?
56
59
  @auto_delete
57
60
  end
58
61
 
59
- def process(visibility, sqs_msg)
62
+ def process(ack_deadline, msg)
60
63
  process_init
61
64
  worker = @worker_class.new
62
- body = get_body(sqs_msg)
63
- timer = visibility_extender visibility, sqs_msg, body, &extend_callback
65
+ body = get_body(msg)
66
+ timer = deadline_extender ack_deadline, msg, body if deadline_extension?
64
67
 
65
- debug "Worker #{queue} starts performing..."
66
- worker.perform sqs_msg, body
67
- debug "Worker #{queue} finishes performing..."
68
- sqs_msg.delete if auto_delete?
68
+ debug "Worker #{@queue} starts performing..."
69
+ worker.perform msg, body
70
+ debug "Worker #{@queue} finishes performing..."
71
+ msg.delete if auto_delete?
69
72
  ensure
70
73
  process_cleanup timer
71
74
  end
@@ -73,56 +76,51 @@ module Toiler
73
76
  def process_init
74
77
  @executing = true
75
78
  @thread = Thread.current
76
- debug "Processor #{queue} begins processing..."
79
+ debug "Processor #{@queue} begins processing..."
77
80
  end
78
81
 
79
82
  def process_cleanup(timer)
80
- debug "Processor #{queue} starts cleanup after perform..."
81
- timer.shutdown if timer
83
+ debug "Processor #{@queue} starts cleanup after perform..."
84
+ timer&.shutdown
82
85
  ::ActiveRecord::Base.clear_active_connections! if defined? ActiveRecord
83
86
  processor_finished
84
87
  @executing = false
85
88
  @thread = nil
86
- debug "Processor #{queue} finished cleanup after perform..."
89
+ debug "Processor #{@queue} finished cleanup after perform..."
87
90
  end
88
91
 
89
92
  def processor_finished
90
93
  fetcher.tell :processor_finished
91
94
  end
92
95
 
93
- def visibility_extender(queue_visibility, sqs_msg, body)
94
- return unless auto_visibility_timeout?
95
-
96
- interval = [1, queue_visibility / 3].max
97
- Concurrent::TimerTask.execute execution_interval: interval,
98
- timeout_interval: interval do |task|
99
- begin
100
- sqs_msg.visibility_timeout = queue_visibility
101
- yield sqs_msg, body if block_given?
102
- rescue StandardError => e
103
- error "Processor #{queue} failed to extend visibility of message - #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
104
- task.shutdown if e.message.include?('ReceiptHandle is invalid')
105
- end
96
+ def deadline_extender(ack_deadline, msg, _body)
97
+ interval = [1, ack_deadline / 3].max
98
+ Concurrent::TimerTask.execute execution_interval: interval do |task|
99
+ msg.modify_ack_deadline! ack_deadline
100
+ rescue StandardError => e
101
+ error "Processor #{@queue} failed to extend ack deadline of message " \
102
+ "- #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
103
+ task.shutdown if e.message.include?('ReceiptHandle is invalid')
106
104
  end
107
105
  end
108
106
 
109
- def get_body(sqs_msg)
110
- if sqs_msg.is_a? Array
111
- sqs_msg.map { |m| parse_body m }
107
+ def get_body(msg)
108
+ if msg.is_a? Array
109
+ msg.map { |m| parse_body m }
112
110
  else
113
- parse_body sqs_msg
111
+ parse_body msg
114
112
  end
115
113
  end
116
114
 
117
- def parse_body(sqs_msg)
118
- case body_parser
119
- when :json then JSON.parse sqs_msg.body
120
- when Proc then body_parser.call sqs_msg
121
- when :text, nil then sqs_msg.body
122
- else body_parser.load sqs_msg.body
115
+ def parse_body(msg)
116
+ case @body_parser
117
+ when :json then JSON.parse msg.body
118
+ when Proc then @body_parser.call msg
119
+ when :text, nil then msg.body
120
+ else @body_parser.load msg.body
123
121
  end
124
122
  rescue StandardError => e
125
- raise "Error parsing the message body: #{e.message}"
123
+ raise "Error parsing the message body: #{e.message} - #{msg.body}"
126
124
  end
127
125
  end
128
126
  end