cloudtasker 0.10.rc2 → 0.10.rc3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -4
- data/README.md +7 -2
- data/gemfiles/google_cloud_tasks_1.0.gemfile +3 -5
- data/gemfiles/google_cloud_tasks_1.1.gemfile +3 -5
- data/gemfiles/google_cloud_tasks_1.2.gemfile +3 -5
- data/gemfiles/google_cloud_tasks_1.3.gemfile +3 -5
- data/gemfiles/rails_5.2.gemfile +3 -5
- data/gemfiles/rails_6.0.gemfile +3 -5
- data/lib/cloudtasker/backend/redis_task.rb +1 -1
- data/lib/cloudtasker/config.rb +20 -2
- data/lib/cloudtasker/local_server.rb +4 -1
- data/lib/cloudtasker/version.rb +1 -1
- data/lib/cloudtasker/worker.rb +54 -14
- data/lib/cloudtasker/worker_logger.rb +3 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b2692b6e4fe2fbd98cf70fb2fe7a2027992d83be63188108113d523cf410160
|
4
|
+
data.tar.gz: 892910f2b59d84e2f516478926fefb63bd913bb4db4507133cde614eca933482
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f7a4bc94036af569901cde01fe7596acc9eea3019d9216db033de2833eb471668f231c6dbf00ac231247f599291fdd0bec0ed4eb700cf06521b01ea2a753739
|
7
|
+
data.tar.gz: f37e39c7ce6fff9a755a457694f5b46aca1c64b9a46b8b1fa74e10208e19c6c6beffc7e5c4e50a74040d1cca8032088478be2168f80f17e2be4d1845d4d69306
|
data/.rubocop.yml
CHANGED
@@ -8,6 +8,9 @@ AllCops:
|
|
8
8
|
Metrics/ClassLength:
|
9
9
|
Max: 150
|
10
10
|
|
11
|
+
Metrics/ModuleLength:
|
12
|
+
Max: 150
|
13
|
+
|
11
14
|
Metrics/AbcSize:
|
12
15
|
Max: 20
|
13
16
|
|
@@ -34,7 +37,4 @@ Metrics/BlockLength:
|
|
34
37
|
Style/Documentation:
|
35
38
|
Exclude:
|
36
39
|
- 'examples/**/*'
|
37
|
-
- 'spec/**/*'
|
38
|
-
|
39
|
-
RSpec/RepeatedExampleGroupBody:
|
40
|
-
Enabled: false
|
40
|
+
- 'spec/**/*'
|
data/README.md
CHANGED
@@ -246,6 +246,8 @@ Cloudtasker.configure do |config|
|
|
246
246
|
# You can set this configuration parameter to a KB value if you want to store jobs
|
247
247
|
# args in redis only if the JSONified arguments payload exceeds that threshold.
|
248
248
|
#
|
249
|
+
# Supported since: v0.10.rc1
|
250
|
+
#
|
249
251
|
# Default: false
|
250
252
|
#
|
251
253
|
# Store all job payloads in Redis:
|
@@ -503,7 +505,7 @@ See the [Cloudtasker::Worker class](lib/cloudtasker/worker.rb) for more informat
|
|
503
505
|
|
504
506
|
## Error Handling
|
505
507
|
|
506
|
-
Jobs failing will automatically return an HTTP error to Cloud Task and trigger a retry at a later time. The number of retries Cloud Task will
|
508
|
+
Jobs failing will automatically return an HTTP error to Cloud Task and trigger a retry at a later time. The number of Cloud Task retries Cloud Task will depend on the configuration of your queue in Cloud Tasks.
|
507
509
|
|
508
510
|
### HTTP Error codes
|
509
511
|
|
@@ -549,6 +551,8 @@ By default jobs are retried 25 times - using an exponential backoff - before bei
|
|
549
551
|
|
550
552
|
Note that the number of retries set on your Cloud Task queue should be many times higher than the number of retries configured in Cloudtasker because Cloud Task also includes failures to connect to your application. Ideally set the number of retries to `unlimited` in Cloud Tasks.
|
551
553
|
|
554
|
+
**Note**: The `X-CloudTasks-TaskExecutionCount` header sent by Google Cloud Tasks and providing the number of retries outside of `HTTP 503` (instance not reachable) is currently bugged and remains at `0` all the time. Starting with `0.10.rc3` Cloudtasker uses the `X-CloudTasks-TaskRetryCount` header to detect the number of retries. This header includes `HTTP 503` errors which means that if your application is down at some point, jobs will fail and these failures will be counted toward the maximum number of retries. A [bug report](https://issuetracker.google.com/issues/154532072) has been raised with GCP to address this issue. Once fixed we will revert to using `X-CloudTasks-TaskExecutionCount` to avoid counting `HTTP 503` as job failures.
|
555
|
+
|
552
556
|
E.g. Set max number of retries globally via the cloudtasker initializer.
|
553
557
|
```ruby
|
554
558
|
# config/initializers/cloudtasker.rb
|
@@ -583,7 +587,6 @@ end
|
|
583
587
|
```
|
584
588
|
|
585
589
|
|
586
|
-
|
587
590
|
## Best practices building workers
|
588
591
|
|
589
592
|
Below are recommendations and notes about creating workers.
|
@@ -658,6 +661,8 @@ Google Cloud Tasks enforces a limit of 100 KB for job payloads. Taking into acco
|
|
658
661
|
Any excessive job payload (> 100 KB) will raise a `Cloudtasker::MaxTaskSizeExceededError`, both in production and development mode.
|
659
662
|
|
660
663
|
#### Option 1: Use Cloudtasker optional support for payload storage in Redis
|
664
|
+
**Supported since**: `0.10.rc1`
|
665
|
+
|
661
666
|
Cloudtasker provides optional support for storing argument payloads in Redis instead of sending them to Google Cloud Tasks.
|
662
667
|
|
663
668
|
To enable it simply put the following in your Cloudtasker initializer:
|
data/gemfiles/rails_5.2.gemfile
CHANGED
data/gemfiles/rails_6.0.gemfile
CHANGED
@@ -248,7 +248,7 @@ module Cloudtasker
|
|
248
248
|
req = Net::HTTP::Post.new(uri.path, http_request[:headers])
|
249
249
|
|
250
250
|
# Add retries header
|
251
|
-
req[
|
251
|
+
req[Cloudtasker::Config::RETRY_HEADER] = retries
|
252
252
|
|
253
253
|
# Set job payload
|
254
254
|
req.body = http_request[:body]
|
data/lib/cloudtasker/config.rb
CHANGED
@@ -13,7 +13,17 @@ module Cloudtasker
|
|
13
13
|
MAX_TASK_SIZE = 100 * 1024 # 100 KB
|
14
14
|
|
15
15
|
# Retry header in Cloud Task responses
|
16
|
-
|
16
|
+
#
|
17
|
+
# TODO: use 'X-CloudTasks-TaskExecutionCount' instead of 'X-CloudTasks-TaskRetryCount'
|
18
|
+
# 'X-CloudTasks-TaskExecutionCount' is currently bugged and remains at 0 even on retries.
|
19
|
+
#
|
20
|
+
# See bug: https://issuetracker.google.com/issues/154532072
|
21
|
+
#
|
22
|
+
# Definitions:
|
23
|
+
# X-CloudTasks-TaskRetryCount: total number of retries (including 504 "instance unreachable")
|
24
|
+
# X-CloudTasks-TaskExecutionCount: number of non-503 retries (= actual number of job failures)
|
25
|
+
#
|
26
|
+
RETRY_HEADER = 'X-CloudTasks-TaskRetryCount'
|
17
27
|
|
18
28
|
# Content-Transfer-Encoding header in Cloud Task responses
|
19
29
|
ENCODING_HEADER = 'Content-Transfer-Encoding'
|
@@ -33,7 +43,15 @@ module Cloudtasker
|
|
33
43
|
DEFAULT_QUEUE_CONCURRENCY = 10
|
34
44
|
DEFAULT_QUEUE_RETRIES = -1 # unlimited
|
35
45
|
|
36
|
-
# The number of times jobs will be attempted before declaring them dead
|
46
|
+
# The number of times jobs will be attempted before declaring them dead.
|
47
|
+
#
|
48
|
+
# With the default retry configuration (maxDoublings = 16 and minBackoff = 0.100s)
|
49
|
+
# it means that jobs will be declared dead after 20h of consecutive failing.
|
50
|
+
#
|
51
|
+
# Note that this configuration parameter is internal to Cloudtasker and does not
|
52
|
+
# affect the Cloud Task queue configuration. The number of retries configured
|
53
|
+
# on the Cloud Task queue should be higher than the number below to also cover
|
54
|
+
# failures due to the instance being unreachable.
|
37
55
|
DEFAULT_MAX_RETRY_ATTEMPTS = 25
|
38
56
|
|
39
57
|
PROCESSOR_HOST_MISSING = <<~DOC
|
@@ -12,6 +12,9 @@ module Cloudtasker
|
|
12
12
|
# Default number of threads to allocate to process a specific queue
|
13
13
|
QUEUE_CONCURRENCY = 1
|
14
14
|
|
15
|
+
# Job Polling. How frequently to poll jobs in redis.
|
16
|
+
JOB_POLLING_FREQUENCY = 0.5 # seconds
|
17
|
+
|
15
18
|
#
|
16
19
|
# Stop the local server.
|
17
20
|
#
|
@@ -46,7 +49,7 @@ module Cloudtasker
|
|
46
49
|
@start ||= Thread.new do
|
47
50
|
until @done
|
48
51
|
queues.each { |(n, c)| process_jobs(n, c) }
|
49
|
-
sleep
|
52
|
+
sleep JOB_POLLING_FREQUENCY
|
50
53
|
end
|
51
54
|
Cloudtasker.logger.info('[Cloudtasker/Server] Local server exiting...')
|
52
55
|
end
|
data/lib/cloudtasker/version.rb
CHANGED
data/lib/cloudtasker/worker.rb
CHANGED
@@ -7,7 +7,8 @@ module Cloudtasker
|
|
7
7
|
def self.included(base)
|
8
8
|
base.extend(ClassMethods)
|
9
9
|
base.attr_writer :job_queue
|
10
|
-
base.attr_accessor :job_args, :job_id, :job_meta, :job_reenqueued, :job_retries
|
10
|
+
base.attr_accessor :job_args, :job_id, :job_meta, :job_reenqueued, :job_retries,
|
11
|
+
:perform_started_at, :perform_ended_at
|
11
12
|
end
|
12
13
|
|
13
14
|
#
|
@@ -181,21 +182,19 @@ module Cloudtasker
|
|
181
182
|
#
|
182
183
|
def execute
|
183
184
|
logger.info('Starting job...')
|
184
|
-
resp = Cloudtasker.config.server_middleware.invoke(self) do
|
185
|
-
begin
|
186
|
-
perform(*job_args)
|
187
|
-
rescue StandardError => e
|
188
|
-
try(:on_error, e)
|
189
|
-
return raise(e) unless job_dead?
|
190
185
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
197
|
-
logger.info('Job done')
|
186
|
+
# Perform job logic
|
187
|
+
resp = execute_middleware_chain
|
188
|
+
|
189
|
+
# Log job completion and return result
|
190
|
+
logger.info("Job done after #{job_duration}s") { { duration: job_duration } }
|
198
191
|
resp
|
192
|
+
rescue DeadWorkerError => e
|
193
|
+
logger.info("Job dead after #{job_duration}s and #{job_retries} retries") { { duration: job_duration } }
|
194
|
+
raise(e)
|
195
|
+
rescue StandardError => e
|
196
|
+
logger.info("Job failed after #{job_duration}s") { { duration: job_duration } }
|
197
|
+
raise(e)
|
199
198
|
end
|
200
199
|
|
201
200
|
#
|
@@ -286,5 +285,46 @@ module Cloudtasker
|
|
286
285
|
def job_dead?
|
287
286
|
job_retries >= Cloudtasker.config.max_retries
|
288
287
|
end
|
288
|
+
|
289
|
+
#
|
290
|
+
# Return the time taken (in seconds) to perform the job. This duration
|
291
|
+
# includes the middlewares and the actual perform method.
|
292
|
+
#
|
293
|
+
# @return [Float] The time taken in seconds as a floating point number.
|
294
|
+
#
|
295
|
+
def job_duration
|
296
|
+
return 0.0 unless perform_ended_at && perform_started_at
|
297
|
+
|
298
|
+
(perform_ended_at - perform_started_at).ceil(3)
|
299
|
+
end
|
300
|
+
|
301
|
+
#=============================
|
302
|
+
# Private
|
303
|
+
#=============================
|
304
|
+
private
|
305
|
+
|
306
|
+
#
|
307
|
+
# Execute the worker perform method through the middleware chain.
|
308
|
+
#
|
309
|
+
# @return [Any] The result of the perform method.
|
310
|
+
#
|
311
|
+
def execute_middleware_chain
|
312
|
+
self.perform_started_at = Time.now
|
313
|
+
|
314
|
+
Cloudtasker.config.server_middleware.invoke(self) do
|
315
|
+
begin
|
316
|
+
perform(*job_args)
|
317
|
+
rescue StandardError => e
|
318
|
+
try(:on_error, e)
|
319
|
+
return raise(e) unless job_dead?
|
320
|
+
|
321
|
+
# Flag job as dead
|
322
|
+
try(:on_dead, e)
|
323
|
+
raise(DeadWorkerError, e)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
ensure
|
327
|
+
self.perform_ended_at = Time.now
|
328
|
+
end
|
289
329
|
end
|
290
330
|
end
|
@@ -59,7 +59,7 @@ module Cloudtasker
|
|
59
59
|
# @return [String] The formatted log message
|
60
60
|
#
|
61
61
|
def formatted_message(msg)
|
62
|
-
"[Cloudtasker][#{worker.job_id}] #{msg}"
|
62
|
+
"[Cloudtasker][#{worker.class}][#{worker.job_id}] #{msg}"
|
63
63
|
end
|
64
64
|
|
65
65
|
#
|
@@ -141,7 +141,8 @@ module Cloudtasker
|
|
141
141
|
# @param [Proc] &block Optional context block.
|
142
142
|
#
|
143
143
|
def log_message(level, msg, &block)
|
144
|
-
|
144
|
+
# Merge log-specific context into worker-specific context
|
145
|
+
payload_block = -> { log_block.call.merge(block&.call || {}) }
|
145
146
|
|
146
147
|
# ActiveSupport::Logger does not support passing a payload through a block on top
|
147
148
|
# of a message.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudtasker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.
|
4
|
+
version: 0.10.rc3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arnaud Lachaume
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-04-
|
11
|
+
date: 2020-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|