cloudtasker 0.13.1 → 0.14.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/lint_rubocop.yml +1 -1
- data/.github/workflows/test_ruby_3.x.yml +1 -0
- data/.gitignore +4 -1
- data/.rubocop.yml +37 -9
- data/Appraisals +0 -12
- data/CHANGELOG.md +31 -0
- data/Gemfile +12 -0
- data/README.md +145 -6
- data/app/controllers/cloudtasker/worker_controller.rb +30 -9
- data/cloudtasker.gemspec +3 -10
- data/docs/CRON_JOBS.md +23 -0
- data/docs/STORABLE_JOBS.md +68 -0
- data/exe/cloudtasker +5 -2
- data/gemfiles/google_cloud_tasks_1.0.gemfile +10 -1
- data/gemfiles/google_cloud_tasks_1.1.gemfile +10 -1
- data/gemfiles/google_cloud_tasks_1.2.gemfile +10 -1
- data/gemfiles/google_cloud_tasks_1.3.gemfile +10 -1
- data/gemfiles/google_cloud_tasks_1.4.gemfile +10 -1
- data/gemfiles/google_cloud_tasks_1.5.gemfile +10 -1
- data/gemfiles/google_cloud_tasks_2.0.gemfile +10 -1
- data/gemfiles/google_cloud_tasks_2.1.gemfile +10 -1
- data/gemfiles/rails_5.2.gemfile +10 -0
- data/gemfiles/rails_6.0.gemfile +10 -0
- data/gemfiles/rails_6.1.gemfile +10 -0
- data/gemfiles/rails_7.0.gemfile +10 -0
- data/gemfiles/semantic_logger_3.4.gemfile +9 -1
- data/gemfiles/semantic_logger_4.6.gemfile +9 -1
- data/gemfiles/semantic_logger_4.7.0.gemfile +9 -1
- data/gemfiles/semantic_logger_4.7.2.gemfile +9 -1
- data/lib/active_job/queue_adapters/cloudtasker_adapter.rb +8 -1
- data/lib/cloudtasker/authenticator.rb +35 -0
- data/lib/cloudtasker/backend/google_cloud_task_v1.rb +2 -4
- data/lib/cloudtasker/backend/google_cloud_task_v2.rb +3 -5
- data/lib/cloudtasker/backend/memory_task.rb +8 -4
- data/lib/cloudtasker/backend/redis_task.rb +10 -4
- data/lib/cloudtasker/batch/batch_progress.rb +18 -14
- data/lib/cloudtasker/batch/job.rb +124 -31
- data/lib/cloudtasker/batch/middleware/server.rb +2 -2
- data/lib/cloudtasker/cli.rb +5 -7
- data/lib/cloudtasker/cloud_task.rb +16 -20
- data/lib/cloudtasker/config.rb +43 -10
- data/lib/cloudtasker/cron/middleware/server.rb +2 -2
- data/lib/cloudtasker/cron/schedule.rb +5 -2
- data/lib/cloudtasker/middleware/chain.rb +1 -1
- data/lib/cloudtasker/redis_client.rb +1 -4
- data/lib/cloudtasker/retry_worker_error.rb +6 -0
- data/lib/cloudtasker/storable/worker.rb +78 -0
- data/lib/cloudtasker/storable.rb +3 -0
- data/lib/cloudtasker/unique_job/conflict_strategy/base_strategy.rb +4 -2
- data/lib/cloudtasker/unique_job/lock/until_executed.rb +4 -4
- data/lib/cloudtasker/unique_job/lock/until_executing.rb +2 -2
- data/lib/cloudtasker/unique_job/lock/while_executing.rb +2 -2
- data/lib/cloudtasker/unique_job/middleware/client.rb +2 -2
- data/lib/cloudtasker/unique_job/middleware/server.rb +2 -2
- data/lib/cloudtasker/version.rb +1 -1
- data/lib/cloudtasker/worker.rb +38 -15
- data/lib/cloudtasker/worker_handler.rb +25 -19
- data/lib/cloudtasker/worker_logger.rb +48 -0
- data/lib/cloudtasker.rb +4 -1
- data/lib/tasks/setup_queue.rake +6 -6
- metadata +9 -145
- data/.github/workflows/test_ruby_2.6.yml +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca57b71a4e3552693d0a64720f423bf7ff1168d5078018a0177c65f09ad681d8
|
4
|
+
data.tar.gz: f886fe29dec2c4ce12286b3e5af843a02e4ea426fbef6db0d2cfcb300418ea34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8312536629168911d321d0630a126f0671eb9e7e6bdb51c86f675ac159d7294f8a1a87bf89d4b4948dae23f4e9411e5673718bc6edac8b73d10592f1a8527744
|
7
|
+
data.tar.gz: a6aeb550354e06413bcd21e3ce47f0b136184c236b4c908104ee1ae51412f4fbcad6cdc3ab392bf991046fe9a1747f32770ca1a0f8d3bddf35dd54813f8b646e
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,27 +1,28 @@
|
|
1
1
|
require: rubocop-rspec
|
2
2
|
|
3
3
|
AllCops:
|
4
|
+
NewCops: enable
|
5
|
+
SuggestExtensions: false
|
6
|
+
TargetRubyVersion: 2.7
|
4
7
|
Exclude:
|
5
8
|
- 'gemfiles/**/*'
|
6
9
|
- 'vendor/**/*'
|
7
10
|
|
8
|
-
# Ruby 3.0: curly braces around last argument has meaning
|
9
|
-
# See: https://github.com/rubocop/rubocop/issues/7641
|
10
|
-
Style/BracesAroundHashParameters:
|
11
|
-
Enabled: false
|
12
|
-
|
13
11
|
Metrics/ClassLength:
|
14
|
-
Max:
|
12
|
+
Max: 300
|
15
13
|
|
16
14
|
Metrics/ModuleLength:
|
17
15
|
Max: 150
|
18
16
|
|
19
17
|
Metrics/AbcSize:
|
20
|
-
Max:
|
18
|
+
Max: 30
|
21
19
|
Exclude:
|
22
20
|
- 'spec/support/*'
|
23
21
|
|
24
|
-
Metrics/
|
22
|
+
Metrics/PerceivedComplexity:
|
23
|
+
Max: 20
|
24
|
+
|
25
|
+
Layout/LineLength:
|
25
26
|
Max: 120
|
26
27
|
|
27
28
|
Metrics/MethodLength:
|
@@ -53,6 +54,13 @@ Style/Documentation:
|
|
53
54
|
Metrics/ParameterLists:
|
54
55
|
CountKeywordArgs: false
|
55
56
|
|
57
|
+
Metrics/CyclomaticComplexity:
|
58
|
+
Max: 15
|
59
|
+
|
60
|
+
Lint/EmptyBlock:
|
61
|
+
Exclude:
|
62
|
+
- 'examples/rails/config/routes.rb'
|
63
|
+
|
56
64
|
RSpec/MessageSpies:
|
57
65
|
Enabled: false
|
58
66
|
|
@@ -62,4 +70,24 @@ RSpec/MultipleExpectations:
|
|
62
70
|
- 'spec/integration/**/*'
|
63
71
|
|
64
72
|
RSpec/AnyInstance:
|
65
|
-
Enabled: false
|
73
|
+
Enabled: false
|
74
|
+
|
75
|
+
RSpec/MultipleMemoizedHelpers:
|
76
|
+
Enabled: false
|
77
|
+
|
78
|
+
RSpec/NoExpectationExample:
|
79
|
+
AllowedPatterns:
|
80
|
+
- ^expect_
|
81
|
+
- ^assert_
|
82
|
+
|
83
|
+
RSpec/IndexedLet:
|
84
|
+
Enabled: false
|
85
|
+
|
86
|
+
RSpec/StubbedMock:
|
87
|
+
Enabled: false
|
88
|
+
|
89
|
+
RSpec/VerifiedDoubles:
|
90
|
+
Exclude:
|
91
|
+
- spec/cloudtasker/cloud_task_spec.rb
|
92
|
+
- spec/cloudtasker/backend/google_cloud_task_v1_spec.rb
|
93
|
+
- spec/cloudtasker/backend/google_cloud_task_v2_spec.rb
|
data/Appraisals
CHANGED
@@ -1,42 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
appraise 'google_cloud_tasks_1.0' do
|
4
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
5
4
|
gem 'google-cloud-tasks', '~> 1.0.0'
|
6
5
|
end
|
7
6
|
|
8
7
|
appraise 'google_cloud_tasks_1.1' do
|
9
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
10
8
|
gem 'google-cloud-tasks', '~> 1.1.0'
|
11
9
|
end
|
12
10
|
|
13
11
|
appraise 'google_cloud_tasks_1.2' do
|
14
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
15
12
|
gem 'google-cloud-tasks', '~> 1.2.0'
|
16
13
|
end
|
17
14
|
|
18
15
|
appraise 'google_cloud_tasks_1.3' do
|
19
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
20
16
|
gem 'google-cloud-tasks', '~> 1.3.0'
|
21
17
|
end
|
22
18
|
|
23
19
|
appraise 'google_cloud_tasks_1.4' do
|
24
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
25
20
|
gem 'google-cloud-tasks', '~> 1.4.0'
|
26
21
|
end
|
27
22
|
|
28
23
|
appraise 'google_cloud_tasks_1.5' do
|
29
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
30
24
|
gem 'google-cloud-tasks', '~> 1.5.0'
|
31
25
|
end
|
32
26
|
|
33
27
|
appraise 'google_cloud_tasks_2.0' do
|
34
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
35
28
|
gem 'google-cloud-tasks', '~> 2.0.0'
|
36
29
|
end
|
37
30
|
|
38
31
|
appraise 'google_cloud_tasks_2.1' do
|
39
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
40
32
|
gem 'google-cloud-tasks', '~> 2.1.0'
|
41
33
|
end
|
42
34
|
|
@@ -65,21 +57,17 @@ if RUBY_VERSION >= '2.7'
|
|
65
57
|
end
|
66
58
|
|
67
59
|
appraise 'semantic_logger_3.4' do
|
68
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
69
60
|
gem 'semantic_logger', '3.4.1'
|
70
61
|
end
|
71
62
|
|
72
63
|
appraise 'semantic_logger_4.6' do
|
73
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
74
64
|
gem 'semantic_logger', '4.6.1'
|
75
65
|
end
|
76
66
|
|
77
67
|
appraise 'semantic_logger_4.7.0' do
|
78
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
79
68
|
gem 'semantic_logger', '4.7.0'
|
80
69
|
end
|
81
70
|
|
82
71
|
appraise 'semantic_logger_4.7.2' do
|
83
|
-
gem 'activesupport', '~> 6.1.0' # ruby 2.6 compatibility
|
84
72
|
gem 'semantic_logger', '4.7.2'
|
85
73
|
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,36 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.14.rc1](https://github.com/keypup-io/cloudtasker/tree/v0.14.rc1) (2024-09-22)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.13.2...v0.14.rc1)
|
6
|
+
|
7
|
+
**Improvements:**
|
8
|
+
- Authentication: To support OIDC and regular Cloudtasker authentication, we moved the Cloudtasker Authentication header from `Authorization` to `X-Cloudtasker-Authorization`. Backward compatibility is maintained for existing jobs.
|
9
|
+
- Authentication: Use signature-based authentication instead of plain tokens. The authentication token now HMACs the content of the job. This approach prevents token from being reused.
|
10
|
+
- Batch Jobs: Batch job progress and statistics are now calculated using counters, instead of checking every job. This is much faster.
|
11
|
+
- Cron Validation: The cron jobs extension now fails epicly if the cron configuration is invalid, instead of failing silently.
|
12
|
+
- GCP OIDC Authentification: It is now possible to specify an Open ID Connect (OIDC) service account to run Cloudtasker on private Cloud Run services. OIDC authentication is provided as an extra authentication layer on top of the regular Cloudtasker authentication system (see below). See the OIDC section in the [initializer documentation](https://github.com/keypup-io/cloudtasker?tab=readme-ov-file#cloudtasker-initializer).
|
13
|
+
- Job Execution Control: Add ability to conditionally raise `Cloudtasker::RetryWorkerError` to retry jobs. This error does not get logged but the retry count will still be increased. This is a safer approach than using the `reenqueue` helper, which can lead to forever running jobs if not used properly. [Documentation](https://github.com/keypup-io/cloudtasker?tab=readme-ov-file#conditional-reenqueues-using-retry-errors).
|
14
|
+
- Log Arguments Truncation: Add `Cloudtasker::WorkerLogger.truncate` helper to truncate large payloads. This is useful to log the top-level attributes of hash/array payloads, without logging the full depth. This case save you significant $$ in logging costs. [Documentation](https://github.com/keypup-io/cloudtasker?tab=readme-ov-file#truncating-log-arguments).
|
15
|
+
- Storable Jobs: Add an interface to park Cloudtasker jobs that need to be conditionally run later. This is useful when you need to capture jobs (and their arguments) during a batch but only enqueue them after the batch is completed. This extension requires Redis and is provided as an optional module. [Documentation](https://github.com/keypup-io/cloudtasker/blob/master/docs/STORABLE_JOBS.md).
|
16
|
+
- Local Server: Add ability to disable SSL verification on the local server when local HTTPS endpoints are used. See the `local_server_ssl_verify` section in the [initializer documentation](https://github.com/keypup-io/cloudtasker?tab=readme-ov-file#cloudtasker-initializer).
|
17
|
+
|
18
|
+
**Fixed bugs:**
|
19
|
+
- ActiveJob: Support `enqueue_after_transaction_commit?` to be ISO with the ActiveJob interface.
|
20
|
+
- Batch Jobs: Do not register batch jobs that were not actually enqueued due to other factors (e.g. Job Uniqueness extension). This issue could lead to never-ending batches.
|
21
|
+
- Duration Logging: Specify the unit (`s` for seconds) on the job duration attribute so it gets properly picked up by GCP Logging. GCP Logging was occasionally mixing up seconds and milliseconds.
|
22
|
+
- Job Retry Count: GCP fixed their retry count header some time ago. We now use the `X-CloudTasks-TaskExecutionCount` header instead of the `X-CloudTasks-TaskRetryCount`. [See more details here](https://github.com/keypup-io/cloudtasker?tab=readme-ov-file#max-retries).
|
23
|
+
- Rails: Use `skip_forgery_protection` instead of `skip_before_action`. The later was causing occasional issues on some setups.
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
## [v0.13.2](https://github.com/keypup-io/cloudtasker/tree/v0.13.2) (2023-07-02)
|
28
|
+
|
29
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.13.1...v0.13.2)
|
30
|
+
|
31
|
+
**Fixed bugs:**
|
32
|
+
- Fix concurrency issue when draining test jobs in `fake!` mode
|
33
|
+
|
3
34
|
## [v0.13.1](https://github.com/keypup-io/cloudtasker/tree/v0.13.1) (2023-06-19)
|
4
35
|
|
5
36
|
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.13.0...v0.13.1)
|
data/Gemfile
CHANGED
@@ -4,3 +4,15 @@ source 'https://rubygems.org'
|
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in cloudtasker.gemspec
|
6
6
|
gemspec
|
7
|
+
|
8
|
+
# Dev dependencies
|
9
|
+
gem 'appraisal', github: 'thoughtbot/appraisal'
|
10
|
+
gem 'bundler', '~> 2.0'
|
11
|
+
gem 'rake', '>= 12.3.3'
|
12
|
+
gem 'rspec', '~> 3.0'
|
13
|
+
gem 'rspec-json_expectations', '~> 2.2'
|
14
|
+
gem 'rubocop', '~> 1.64.1'
|
15
|
+
gem 'rubocop-rspec', '~> 3.0.1'
|
16
|
+
gem 'semantic_logger'
|
17
|
+
gem 'timecop'
|
18
|
+
gem 'webmock'
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
 [](https://badge.fury.io/rb/cloudtasker)
|
2
2
|
|
3
|
+
🚀🚀🚀 Cloudtasker 0.14 release candidate (`v0.14.rc1`) is out and it's quite big ([Changelog](https://github.com/keypup-io/cloudtasker/blob/master/CHANGELOG.md)). Any help testing this release is welcome, and feel free to open issues if you spot any regression.
|
4
|
+
|
3
5
|
# Cloudtasker
|
4
6
|
|
5
7
|
Background jobs for Ruby using Google Cloud Tasks.
|
@@ -8,7 +10,7 @@ Cloudtasker provides an easy to manage interface to Google Cloud Tasks for backg
|
|
8
10
|
|
9
11
|
Cloudtasker is particularly suited for serverless applications only responding to HTTP requests and where running a dedicated job processing server is not an option (e.g. deploy via [Cloud Run](https://cloud.google.com/run)). All jobs enqueued in Cloud Tasks via Cloudtasker eventually get processed by your application via HTTP requests.
|
10
12
|
|
11
|
-
Cloudtasker also provides optional modules for running [cron jobs](docs/CRON_JOBS.md), [batch jobs](docs/BATCH_JOBS.md)
|
13
|
+
Cloudtasker also provides optional modules for running [cron jobs](docs/CRON_JOBS.md), [batch jobs](docs/BATCH_JOBS.md), [unique jobs](docs/UNIQUE_JOBS.md) and [storable jobs](docs/STORABLE_JOBS.md).
|
12
14
|
|
13
15
|
A local processing server is also available for development. This local server processes jobs in lieu of Cloud Tasks and allows you to work offline.
|
14
16
|
|
@@ -31,12 +33,15 @@ A local processing server is also available for development. This local server p
|
|
31
33
|
8. [Logging](#logging)
|
32
34
|
1. [Configuring a logger](#configuring-a-logger)
|
33
35
|
2. [Logging context](#logging-context)
|
36
|
+
3. [Truncating log arguments](#truncating-log-arguments)
|
37
|
+
4. [Searching logs: Job ID vs Task ID](#searching-logs-job-id-vs-task-id)
|
34
38
|
9. [Error Handling](#error-handling)
|
35
39
|
1. [HTTP Error codes](#http-error-codes)
|
36
40
|
2. [Worker callbacks](#worker-callbacks)
|
37
41
|
3. [Global callbacks](#global-callbacks)
|
38
42
|
4. [Max retries](#max-retries)
|
39
|
-
5. [
|
43
|
+
5. [Conditional reenqueues using retry errors](#conditional-reenqueues-using-retry-errors)
|
44
|
+
6. [Dispatch deadline](#dispatch-deadline)
|
40
45
|
10. [Testing](#testing)
|
41
46
|
1. [Test helper setup](#test-helper-setup)
|
42
47
|
2. [In-memory queues](#in-memory-queues)
|
@@ -137,7 +142,7 @@ Now jump to the next section to configure your app to use Google Cloud Tasks as
|
|
137
142
|
|
138
143
|
## Get started with Rails & ActiveJob
|
139
144
|
**Note**: ActiveJob is supported since `0.11.0`
|
140
|
-
**Note**: Cloudtasker extensions (cron, batch
|
145
|
+
**Note**: Cloudtasker extensions (cron, batch, unique jobs and storable) are not available when using cloudtasker via ActiveJob.
|
141
146
|
|
142
147
|
Cloudtasker is pre-integrated with ActiveJob. Follow the steps below to get started.
|
143
148
|
|
@@ -328,7 +333,8 @@ Cloudtasker.configure do |config|
|
|
328
333
|
# Specify the redis connection hash.
|
329
334
|
#
|
330
335
|
# This is ONLY required in development for the Cloudtasker local server and in
|
331
|
-
# all environments if you use any cloudtasker extension (unique jobs, cron jobs
|
336
|
+
# all environments if you use any cloudtasker extension (unique jobs, cron jobs,
|
337
|
+
# batch jobs or storable jobs)
|
332
338
|
#
|
333
339
|
# See https://github.com/redis/redis-rb for examples of configuration hashes.
|
334
340
|
#
|
@@ -370,6 +376,8 @@ Cloudtasker.configure do |config|
|
|
370
376
|
# Supported since: v0.12.0
|
371
377
|
#
|
372
378
|
# Default: 600 seconds (10 minutes)
|
379
|
+
# Min: 15 seconds
|
380
|
+
# Max: 1800 seconds (30 minutes)
|
373
381
|
#
|
374
382
|
# config.dispatch_deadline = 600
|
375
383
|
|
@@ -402,6 +410,40 @@ Cloudtasker.configure do |config|
|
|
402
410
|
# Default: no operation
|
403
411
|
#
|
404
412
|
# config.on_dead = ->(error, worker) { Rollbar.error(error) }
|
413
|
+
|
414
|
+
#
|
415
|
+
# Specify the Open ID Connect (OIDC) details to connect to a protected GCP service, such
|
416
|
+
# as a private Cloud Run application.
|
417
|
+
#
|
418
|
+
# The configuration supports the following details:
|
419
|
+
# - service_account_email: This is the "act as" user. It can be found under the security details
|
420
|
+
# of the Cloud Run service.
|
421
|
+
# - audience: The audience is usually the publicly accessible host for the Cloud Run service
|
422
|
+
# (which is the same value configured as the processor_host). If no audiences are provided
|
423
|
+
# it will be set to the processor_host.
|
424
|
+
#
|
425
|
+
# Note: If the OIDC token is used for a Cloud Run service make sure to include the
|
426
|
+
# `iam.serviceAccounts.actAs` permission on the service account.
|
427
|
+
#
|
428
|
+
# See https://cloud.google.com/tasks/docs/creating-http-target-tasks#sa for more information on
|
429
|
+
# setting up service accounts for use with Cloud Tasks.
|
430
|
+
#
|
431
|
+
# Supported since: v0.14.rc1
|
432
|
+
#
|
433
|
+
# Default: nil
|
434
|
+
#
|
435
|
+
# config.oidc = { service_account_email: 'example@gserviceaccount.com' }
|
436
|
+
# config.oidc = { service_account_email: 'example@gserviceaccount.com', audience: 'https://api.example.net' }
|
437
|
+
|
438
|
+
#
|
439
|
+
# Enable/disable the verification of SSL certificates on the local processing server when
|
440
|
+
# sending tasks to the processor.
|
441
|
+
#
|
442
|
+
# Set to false to disable SSL verification (OpenSSL::SSL::VERIFY_NONE).
|
443
|
+
#
|
444
|
+
# Default: true
|
445
|
+
#
|
446
|
+
# config.local_server_ssl_verify = true
|
405
447
|
end
|
406
448
|
```
|
407
449
|
|
@@ -449,6 +491,8 @@ class FetchResourceWorker
|
|
449
491
|
# ...do some logic...
|
450
492
|
if some_condition
|
451
493
|
# Stop and re-enqueue the job to be run again in 10 seconds.
|
494
|
+
# Also see the section on Cloudtasker::RetryWorkerError for a different
|
495
|
+
# approach on reenqueuing.
|
452
496
|
return reenqueue(10)
|
453
497
|
else
|
454
498
|
# ...keep going...
|
@@ -510,6 +554,7 @@ Cloudtasker comes with three optional features:
|
|
510
554
|
- Cron Jobs [[docs](docs/CRON_JOBS.md)]: Run jobs at fixed intervals.
|
511
555
|
- Batch Jobs [[docs](docs/BATCH_JOBS.md)]: Run jobs in jobs and track completion of the overall batch.
|
512
556
|
- Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
|
557
|
+
- Storable Jobs [[docs](docs/STORABLE_JOBS.md)]: Park jobs until they are ready to be enqueued.
|
513
558
|
|
514
559
|
## Working locally
|
515
560
|
|
@@ -652,6 +697,60 @@ end
|
|
652
697
|
|
653
698
|
See the [Cloudtasker::Worker class](lib/cloudtasker/worker.rb) for more information on attributes available to be logged in your `log_context_processor` proc.
|
654
699
|
|
700
|
+
### Truncating log arguments
|
701
|
+
**Supported since**: `v0.14.rc1`
|
702
|
+
|
703
|
+
By default Cloudtasker does not log job arguments as arguments can contain sensitive data and generate voluminous logs, which may lead to noticeable costs with your log provider (e.g. GCP Logging). Also some providers (e.g. GCP Logging) will automatically truncate log entries that are too big and reduce their searchability.
|
704
|
+
|
705
|
+
Job arguments can be logged for all workers by configuring the following log context processor in your Cloudtasker initializer:
|
706
|
+
```ruby
|
707
|
+
Cloudtasker::WorkerLogger.log_context_processor = ->(worker) { worker.to_h }
|
708
|
+
```
|
709
|
+
|
710
|
+
In order to reduce the size of logged job arguments, the following `truncate` utility is provided by Cloudtasker:
|
711
|
+
```ruby
|
712
|
+
# string_limit: The maximum size for strings. Default is 64. Set to -1 to disable.
|
713
|
+
# array_limit: The maximum length for arrays. Default is 10. Set to -1 to disable.
|
714
|
+
# max_depth: The maximum recursive depth. Default is 3. Set to -1 to disable.
|
715
|
+
Cloudtasker::WorkerLogger.truncate(payload, string_limit: 64, array_limit: 10, max_depth: 3)
|
716
|
+
```
|
717
|
+
|
718
|
+
You may use it the following way:
|
719
|
+
```ruby
|
720
|
+
Cloudtasker::WorkerLogger.log_context_processor = lambda do |worker|
|
721
|
+
payload = worker.to_h
|
722
|
+
|
723
|
+
# Using default options
|
724
|
+
payload[:job_args] = Cloudtasker::WorkerLogger.truncate(payload[:job_args])
|
725
|
+
|
726
|
+
# Using custom options
|
727
|
+
# payload[:job_args] = Cloudtasker::WorkerLogger.truncate(payload[:job_args], string_limit: 32, array_limit: 5, max_depth: 2)
|
728
|
+
|
729
|
+
# Return the payload to log
|
730
|
+
payload
|
731
|
+
end
|
732
|
+
```
|
733
|
+
|
734
|
+
To further reduce logging cost, you may also log a reasonably complete version of job arguments at start then log a watered down version for the remaining log entries:
|
735
|
+
```ruby
|
736
|
+
Cloudtasker::WorkerLogger.log_context_processor = lambda do |worker|
|
737
|
+
payload = worker.to_h
|
738
|
+
|
739
|
+
# Adjust the log payload based on the lifecycle of the job
|
740
|
+
payload[:job_args] = if worker.perform_started_at
|
741
|
+
# The job start has already been logged. Log the job primitive arguments without depth.
|
742
|
+
# Arrays and hashes will be masked.
|
743
|
+
Cloudtasker::WorkerLogger.truncate(payload[:job_args], max_depth: 0)
|
744
|
+
else
|
745
|
+
# This is the job start. Log a more complete version of the job args.
|
746
|
+
Cloudtasker::WorkerLogger.truncate(payload[:job_args])
|
747
|
+
end
|
748
|
+
|
749
|
+
# Return the payload to log
|
750
|
+
payload
|
751
|
+
end
|
752
|
+
```
|
753
|
+
|
655
754
|
### Searching logs: Job ID vs Task ID
|
656
755
|
**Note**: `task_id` field is available in logs starting with `0.10.0`
|
657
756
|
|
@@ -735,7 +834,7 @@ By default jobs are retried 25 times - using an exponential backoff - before bei
|
|
735
834
|
|
736
835
|
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.
|
737
836
|
|
738
|
-
**Note**:
|
837
|
+
**Note**: Versions prior to `v0.14.rc1` use the `X-CloudTasks-TaskRetryCount` header for retries instead of the `X-CloudTasks-TaskExecutionCount` header to detect the number of retries, because there a previous bug on the GCP side which made the `X-CloudTasks-TaskExecutionCount` stay at zero instead of increasing on successive executions. Versions prior to `v0.14.rc1` count any failure as failure, including failures due to the backend being unavailable (`HTTP 503`). Versions `v0.14.rc1` and later only count application failure (`HTTP 4xx`) as failure for retry purpose.
|
739
838
|
|
740
839
|
E.g. Set max number of retries globally via the cloudtasker initializer.
|
741
840
|
```ruby
|
@@ -791,6 +890,46 @@ class SomeErrorWorker
|
|
791
890
|
end
|
792
891
|
```
|
793
892
|
|
893
|
+
### Conditional reenqueues using retry errors
|
894
|
+
**Supported since**: `v0.14.rc1`
|
895
|
+
|
896
|
+
If your worker is waiting for some precondition to occur and you want to re-enqueue it until the condition has been met, you can raise a `Cloudtasker::RetryWorkerError`. This special error will fail your job **without logging an error** while still increasing the number of retries.
|
897
|
+
|
898
|
+
This is a safer approach than using the `reenqueue` helper, which can lead to forever running jobs if not used properly.
|
899
|
+
|
900
|
+
```ruby
|
901
|
+
# app/workers/my_worker.rb
|
902
|
+
|
903
|
+
class MyWorker
|
904
|
+
include Cloudtasker::Worker
|
905
|
+
|
906
|
+
def perform(project_id)
|
907
|
+
# Abort if project does not exist
|
908
|
+
return unless (project = Project.find_by(id: project_id))
|
909
|
+
|
910
|
+
# Trigger a retry if the project is still in "discovering" status
|
911
|
+
# This error will NOT log an error. It only triggers a retry.
|
912
|
+
raise Cloudtasker::RetryWorkerError if project.status == 'discovering'
|
913
|
+
|
914
|
+
# The previous approach was to use `reenqueue`. This works but since it
|
915
|
+
# does not increase the number of retries, you may end up with forever running
|
916
|
+
# jobs
|
917
|
+
# return reenqueue(10) if project.status == 'discovering'
|
918
|
+
|
919
|
+
# Do stuff when project is not longer discovering
|
920
|
+
do_some_stuff
|
921
|
+
end
|
922
|
+
|
923
|
+
# You can then specify what should be done if we've been waiting for too long
|
924
|
+
def on_dead(error)
|
925
|
+
logger.error("Looks like the project is forever discovering. Time to give up.")
|
926
|
+
|
927
|
+
# This is of course an imaginary method
|
928
|
+
send_slack_notification_to_internal_support_team(worker: self.class, args: job_args)
|
929
|
+
end
|
930
|
+
end
|
931
|
+
```
|
932
|
+
|
794
933
|
### Dispatch deadline
|
795
934
|
**Supported since**: `0.12.0`
|
796
935
|
|
@@ -1092,7 +1231,7 @@ To size the concurrency of your queues you should therefore take the most limiti
|
|
1092
1231
|
After checking out the repo, run `bin/setup` to install dependencies.
|
1093
1232
|
|
1094
1233
|
For tests, run `rake` to run the tests. Note that Rails is not in context by default, which means Rails-specific test will not run.
|
1095
|
-
For tests including Rails-specific tests, run `bundle exec appraisal
|
1234
|
+
For tests including Rails-specific tests, run `bundle exec appraisal rails_7.0 rake`
|
1096
1235
|
For all context-specific tests (incl. Rails), run the [appraisal tests](Appraisals) using `bundle exec appraisal rake`.
|
1097
1236
|
|
1098
1237
|
You can run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -4,7 +4,7 @@ module Cloudtasker
|
|
4
4
|
# Handle execution of workers
|
5
5
|
class WorkerController < ActionController::Base
|
6
6
|
# No need for CSRF verification on API endpoints
|
7
|
-
|
7
|
+
skip_forgery_protection
|
8
8
|
|
9
9
|
# Authenticate all requests.
|
10
10
|
before_action :authenticate!
|
@@ -36,13 +36,12 @@ module Cloudtasker
|
|
36
36
|
private
|
37
37
|
|
38
38
|
#
|
39
|
-
# Parse the request body and return the
|
40
|
-
# payload.
|
39
|
+
# Parse the request body and return the JSON payload
|
41
40
|
#
|
42
|
-
# @return [
|
41
|
+
# @return [String] The JSON payload
|
43
42
|
#
|
44
|
-
def
|
45
|
-
@
|
43
|
+
def json_payload
|
44
|
+
@json_payload ||= begin
|
46
45
|
# Get raw body
|
47
46
|
content = request.body.read
|
48
47
|
|
@@ -51,11 +50,22 @@ module Cloudtasker
|
|
51
50
|
content = Base64.decode64(content)
|
52
51
|
end
|
53
52
|
|
54
|
-
# Return content
|
55
|
-
|
53
|
+
# Return the content
|
54
|
+
content
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
58
|
+
#
|
59
|
+
# Parse the request body and return the actual job
|
60
|
+
# payload.
|
61
|
+
#
|
62
|
+
# @return [Hash] The job payload
|
63
|
+
#
|
64
|
+
def payload
|
65
|
+
# Return content parsed as JSON and add job retries count
|
66
|
+
@payload ||= JSON.parse(json_payload).merge(job_retries: job_retries, task_id: task_id)
|
67
|
+
end
|
68
|
+
|
59
69
|
#
|
60
70
|
# Extract the number of times this task failed at runtime.
|
61
71
|
#
|
@@ -80,7 +90,18 @@ module Cloudtasker
|
|
80
90
|
# See Cloudtasker::Authenticator#verification_token
|
81
91
|
#
|
82
92
|
def authenticate!
|
83
|
-
|
93
|
+
if (signature = request.headers[Cloudtasker::Config::CT_SIGNATURE_HEADER])
|
94
|
+
# Verify content signature
|
95
|
+
Authenticator.verify_signature!(signature, json_payload)
|
96
|
+
else
|
97
|
+
# Get authorization token from custom header (since v0.14.rc1) or fallback to
|
98
|
+
# former authorization header (jobs enqueued by v0.13 and below)
|
99
|
+
bearer_token = request.headers[Cloudtasker::Config::CT_AUTHORIZATION_HEADER].to_s.split.last ||
|
100
|
+
request.headers[Cloudtasker::Config::OIDC_AUTHORIZATION_HEADER].to_s.split.last
|
101
|
+
|
102
|
+
# Verify the token
|
103
|
+
Authenticator.verify!(bearer_token)
|
104
|
+
end
|
84
105
|
end
|
85
106
|
end
|
86
107
|
end
|
data/cloudtasker.gemspec
CHANGED
@@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
29
|
spec.require_paths = ['lib']
|
30
30
|
|
31
|
+
spec.required_ruby_version = '>= 2.7.0'
|
32
|
+
|
31
33
|
spec.add_dependency 'activesupport'
|
32
34
|
spec.add_dependency 'connection_pool'
|
33
35
|
spec.add_dependency 'fugit'
|
@@ -36,14 +38,5 @@ Gem::Specification.new do |spec|
|
|
36
38
|
spec.add_dependency 'redis'
|
37
39
|
spec.add_dependency 'retriable'
|
38
40
|
|
39
|
-
spec.
|
40
|
-
spec.add_development_dependency 'bundler', '~> 2.0'
|
41
|
-
spec.add_development_dependency 'rake', '>= 12.3.3'
|
42
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
43
|
-
spec.add_development_dependency 'rspec-json_expectations', '~> 2.2'
|
44
|
-
spec.add_development_dependency 'rubocop', '0.76.0'
|
45
|
-
spec.add_development_dependency 'rubocop-rspec', '1.37.0'
|
46
|
-
spec.add_development_dependency 'semantic_logger'
|
47
|
-
spec.add_development_dependency 'timecop'
|
48
|
-
spec.add_development_dependency 'webmock'
|
41
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
49
42
|
end
|
data/docs/CRON_JOBS.md
CHANGED
@@ -65,6 +65,29 @@ if File.exist?(schedule_file) && !Rails.env.test?
|
|
65
65
|
end
|
66
66
|
```
|
67
67
|
|
68
|
+
## With Puma Cluster-mode
|
69
|
+
Due to this issue with gRPC here: https://github.com/grpc/grpc/issues/7951.
|
70
|
+
|
71
|
+
TLTR:
|
72
|
+
> Forking processes and using gRPC across processes is not supported behavior due to very low-level resource issues. Either delay your use of gRPC until you've forked from fresh processes (similar to Python 3's use of a zygote process), or don't expect things to work after a fork.
|
73
|
+
|
74
|
+
In order to make it works, we should schedule cron jobs (which triggers gPRC calls) once puma is booted.
|
75
|
+
|
76
|
+
Example:
|
77
|
+
```ruby
|
78
|
+
config/puma.rb
|
79
|
+
|
80
|
+
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
81
|
+
preload_app!
|
82
|
+
|
83
|
+
on_booted do
|
84
|
+
schedule_file = "config/cloudtasker_cron.yml"
|
85
|
+
if File.exist?(schedule_file) && !Rails.env.test?
|
86
|
+
Cloudtasker::Cron::Schedule.load_from_hash!(YAML.load_file(schedule_file))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
68
91
|
## Limitations
|
69
92
|
GCP Cloud Tasks does not allow tasks to be scheduled more than 30 days (720h) in the future. Cron schedules should therefore be limited to 30 days intervals at most.
|
70
93
|
|