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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9de65884c24b1902d30af0f9b140caf36749d40e4613a13fa88940593e9cd12
4
- data.tar.gz: 9033cd6fca6bab0dc5a1387c6e8a8f82a12460e86165072d987282e5bb0e2b6d
3
+ metadata.gz: 4b2692b6e4fe2fbd98cf70fb2fe7a2027992d83be63188108113d523cf410160
4
+ data.tar.gz: 892910f2b59d84e2f516478926fefb63bd913bb4db4507133cde614eca933482
5
5
  SHA512:
6
- metadata.gz: edfcbf887b455566b52718e06a41dd5061de05e4e93a7a2e2df42d148d863853852545374f27d8b41e4b445866bc0f76fc8ad3866abe182dea7728a302664a1c
7
- data.tar.gz: 631b9929e8b4365e64d47eca6354765773ee79780b60c857a41bca41fd8d7c55f946c621876167deb9067f446e0fbdc54ad2673358f59d0143368203ea8195b9
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 do depends on the configuration of your queue in Cloud Tasks.
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:
@@ -1,9 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  # This file was generated by Appraisal
4
2
 
5
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
6
4
 
7
- gem 'google-cloud-tasks', '1.0'
5
+ gem "google-cloud-tasks", "1.0"
8
6
 
9
- gemspec path: '../'
7
+ gemspec path: "../"
@@ -1,9 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  # This file was generated by Appraisal
4
2
 
5
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
6
4
 
7
- gem 'google-cloud-tasks', '1.1'
5
+ gem "google-cloud-tasks", "1.1"
8
6
 
9
- gemspec path: '../'
7
+ gemspec path: "../"
@@ -1,9 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  # This file was generated by Appraisal
4
2
 
5
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
6
4
 
7
- gem 'google-cloud-tasks', '1.2'
5
+ gem "google-cloud-tasks", "1.2"
8
6
 
9
- gemspec path: '../'
7
+ gemspec path: "../"
@@ -1,9 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  # This file was generated by Appraisal
4
2
 
5
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
6
4
 
7
- gem 'google-cloud-tasks', '1.3'
5
+ gem "google-cloud-tasks", "1.3"
8
6
 
9
- gemspec path: '../'
7
+ gemspec path: "../"
@@ -1,9 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  # This file was generated by Appraisal
4
2
 
5
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
6
4
 
7
- gem 'rails', '5.2'
5
+ gem "rails", "5.2"
8
6
 
9
- gemspec path: '../'
7
+ gemspec path: "../"
@@ -1,9 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  # This file was generated by Appraisal
4
2
 
5
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
6
4
 
7
- gem 'rails', '6.0'
5
+ gem "rails", "6.0"
8
6
 
9
- gemspec path: '../'
7
+ gemspec path: "../"
@@ -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['X-CloudTasks-TaskExecutionCount'] = retries
251
+ req[Cloudtasker::Config::RETRY_HEADER] = retries
252
252
 
253
253
  # Set job payload
254
254
  req.body = http_request[:body]
@@ -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
- RETRY_HEADER = 'X-CloudTasks-TaskExecutionCount'
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 1
52
+ sleep JOB_POLLING_FREQUENCY
50
53
  end
51
54
  Cloudtasker.logger.info('[Cloudtasker/Server] Local server exiting...')
52
55
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cloudtasker
4
- VERSION = '0.10.rc2'
4
+ VERSION = '0.10.rc3'
5
5
  end
@@ -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
- # Flag job as dead
192
- logger.info('Job dead')
193
- try(:on_dead, e)
194
- raise(DeadWorkerError, e)
195
- end
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
- payload_block = block || log_block
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.rc2
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-20 00:00:00.000000000 Z
11
+ date: 2020-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport