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 +4 -4
- data/.rubocop.yml +29 -0
- data/.ruby-version +1 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +130 -42
- data/README.md +13 -13
- data/Rakefile +2 -0
- data/lib/toiler/actor/fetcher.rb +83 -48
- data/lib/toiler/actor/processor.rb +42 -44
- data/lib/toiler/actor/supervisor.rb +7 -5
- data/lib/toiler/actor/utils/actor_logging.rb +5 -3
- data/lib/toiler/aws/message.rb +3 -1
- data/lib/toiler/aws/queue.rb +19 -9
- data/lib/toiler/cli.rb +38 -32
- data/lib/toiler/gcp/message.rb +55 -0
- data/lib/toiler/gcp/queue.rb +37 -0
- data/lib/toiler/utils/argument_parser.rb +2 -0
- data/lib/toiler/utils/environment_loader.rb +16 -18
- data/lib/toiler/utils/logging.rb +5 -3
- data/lib/toiler/version.rb +3 -1
- data/lib/toiler/worker.rb +12 -4
- data/lib/toiler.rb +14 -4
- data/spec/models/fetcher_spec.rb +31 -5
- data/spec/models/supervisor_spec.rb +14 -3
- data/toiler.gemspec +8 -3
- metadata +41 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6565dc34ea694f3407a7bf58108134a277197f1e145990749e0de7cb53f9d02f
|
|
4
|
+
data.tar.gz: 13f9b2aa0c289b4e66098e942c15ef79e402db53ccd67fd8b06bafba0c292377
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
1
|
+
2.6.8
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,60 +1,148 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
toiler (0.
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
aws-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
aws-
|
|
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.
|
|
21
|
-
aws-sdk-core (~> 3, >= 3.
|
|
22
|
-
aws-sigv4 (~> 1.
|
|
23
|
-
aws-sigv4 (1.0
|
|
24
|
-
|
|
25
|
-
concurrent-ruby
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
44
|
-
rspec-mocks (3.
|
|
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.
|
|
47
|
-
rspec-support (3.
|
|
48
|
-
rubocop (
|
|
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 (>=
|
|
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 (
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
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
|
|
23
|
+
Workers can configure a parser Class or Proc to parse a message body before being processed.
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### Deadline Extension
|
|
26
26
|
|
|
27
|
-
Toiler
|
|
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
|
|
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:
|
|
86
|
-
secret_access_key:
|
|
87
|
-
region:
|
|
88
|
-
|
|
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
data/lib/toiler/actor/fetcher.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
13
|
+
attr_reader :queue, :wait, :ack_deadline, :free_processors,
|
|
14
|
+
:executing, :waiting_messages, :concurrency,
|
|
15
|
+
:scheduled_task
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
def initialize(queue_name, count, provider)
|
|
18
|
+
super()
|
|
14
19
|
|
|
15
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
#
|
|
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
|
|
51
|
-
|
|
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 :
|
|
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
|
|
70
|
+
def pull_future(max_number_of_messages)
|
|
65
71
|
Concurrent::Promises.future do
|
|
66
|
-
queue.receive_messages
|
|
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
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
return if waiting_messages > 0 && !full_batch?(max_number_of_messages)
|
|
117
|
+
return unless should_pull?
|
|
82
118
|
|
|
83
|
-
|
|
119
|
+
current_needed_messages = needed_messages
|
|
84
120
|
|
|
85
|
-
|
|
86
|
-
|
|
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,
|
|
89
|
-
tell :
|
|
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,
|
|
94
|
-
tell :
|
|
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 :
|
|
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
|
|
106
|
-
|
|
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,
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
#
|
|
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
|
-
@
|
|
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
|
|
52
|
-
@
|
|
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(
|
|
62
|
+
def process(ack_deadline, msg)
|
|
60
63
|
process_init
|
|
61
64
|
worker = @worker_class.new
|
|
62
|
-
body = get_body(
|
|
63
|
-
timer =
|
|
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
|
|
67
|
-
debug "Worker #{queue} finishes performing..."
|
|
68
|
-
|
|
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
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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(
|
|
110
|
-
if
|
|
111
|
-
|
|
107
|
+
def get_body(msg)
|
|
108
|
+
if msg.is_a? Array
|
|
109
|
+
msg.map { |m| parse_body m }
|
|
112
110
|
else
|
|
113
|
-
parse_body
|
|
111
|
+
parse_body msg
|
|
114
112
|
end
|
|
115
113
|
end
|
|
116
114
|
|
|
117
|
-
def parse_body(
|
|
118
|
-
case body_parser
|
|
119
|
-
when :json then JSON.parse
|
|
120
|
-
when Proc then body_parser.call
|
|
121
|
-
when :text, nil then
|
|
122
|
-
else body_parser.load
|
|
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
|