cloudtasker 0.10.rc6 → 0.11.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +7 -3
- data/.rubocop.yml +4 -1
- data/Appraisals +16 -0
- data/CHANGELOG.md +41 -4
- data/README.md +154 -37
- data/app/controllers/cloudtasker/worker_controller.rb +15 -4
- data/cloudtasker.gemspec +2 -1
- data/gemfiles/semantic_logger_3.4.gemfile +7 -0
- data/gemfiles/semantic_logger_4.6.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.0.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.2.gemfile +7 -0
- data/gemfiles/semantic_logger_4.7.gemfile +7 -0
- data/lib/cloudtasker.rb +1 -0
- data/lib/cloudtasker/backend/memory_task.rb +15 -2
- data/lib/cloudtasker/missing_worker_arguments_error.rb +6 -0
- data/lib/cloudtasker/version.rb +1 -1
- data/lib/cloudtasker/worker.rb +22 -1
- data/lib/cloudtasker/worker_handler.rb +1 -1
- data/lib/cloudtasker/worker_logger.rb +1 -1
- metadata +26 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a0759638a4af47fcc26467b93039b3c6355908db5cafd0a20972b7e48649b81
|
4
|
+
data.tar.gz: a0f76c953bc0f64276f5b10f90300bcf7415dbfe305fac6697e4bd9fb83b53c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef16621727a56793623e1c582bac1f6e4971b8ff1af3ee36a3d37b22d239f7bb3504a35b5f1ffc9f250343b2ed5ae888e75b8d583b49487f34dc69eb45cd9d13
|
7
|
+
data.tar.gz: aea0fa6eb2873f6b99df05c613d89363a3c6103fe814123d25a7508ede1fbf92cf653d10e7d57863d7ec6065d1c96e5c5f3ecf371a75ac925a588693849c320e
|
data/.github/workflows/test.yml
CHANGED
@@ -2,9 +2,9 @@ name: Test
|
|
2
2
|
|
3
3
|
on:
|
4
4
|
push:
|
5
|
-
branches: [ master ]
|
5
|
+
branches: [ master, 0.9-stable ]
|
6
6
|
pull_request:
|
7
|
-
branches: [ master ]
|
7
|
+
branches: [ master, 0.9-stable ]
|
8
8
|
|
9
9
|
jobs:
|
10
10
|
build:
|
@@ -21,6 +21,10 @@ jobs:
|
|
21
21
|
- 'google-cloud-tasks-1.3'
|
22
22
|
- 'rails-5.2'
|
23
23
|
- 'rails-6.0'
|
24
|
+
- 'semantic_logger-3.4'
|
25
|
+
- 'semantic_logger-4.6'
|
26
|
+
- 'semantic_logger-4.7.0'
|
27
|
+
- 'semantic_logger-4.7.2'
|
24
28
|
steps:
|
25
29
|
- name: Setup System
|
26
30
|
run: sudo apt-get install libsqlite3-dev
|
@@ -38,4 +42,4 @@ jobs:
|
|
38
42
|
bundle install --jobs 4 --retry 3
|
39
43
|
bundle exec rubocop
|
40
44
|
bundle exec appraisal ${APPRAISAL_CONTEXT} bundle
|
41
|
-
bundle exec appraisal ${APPRAISAL_CONTEXT} rspec
|
45
|
+
bundle exec appraisal ${APPRAISAL_CONTEXT} rspec
|
data/.rubocop.yml
CHANGED
data/Appraisals
CHANGED
@@ -23,3 +23,19 @@ end
|
|
23
23
|
appraise 'rails-6.0' do
|
24
24
|
gem 'rails', '6.0'
|
25
25
|
end
|
26
|
+
|
27
|
+
appraise 'semantic_logger-3.4' do
|
28
|
+
gem 'semantic_logger', '3.4.1'
|
29
|
+
end
|
30
|
+
|
31
|
+
appraise 'semantic_logger-4.6' do
|
32
|
+
gem 'semantic_logger', '4.6.1'
|
33
|
+
end
|
34
|
+
|
35
|
+
appraise 'semantic_logger-4.7.0' do
|
36
|
+
gem 'semantic_logger', '4.7.0'
|
37
|
+
end
|
38
|
+
|
39
|
+
appraise 'semantic_logger-4.7.2' do
|
40
|
+
gem 'semantic_logger', '4.7.2'
|
41
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,46 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.10.1](https://github.com/keypup-io/cloudtasker/tree/v0.10.1) (2020-10-05)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.10.0...v0.10.1)
|
6
|
+
|
7
|
+
**Fixed bugs:**
|
8
|
+
- Local server: delete dead task from local server queue
|
9
|
+
- Logging: fix log processing with `semantic_logger` `v4.7.2`. Accept any args on block passed to the logger.
|
10
|
+
- Worker: fix configuration of `max_retries` at worker level
|
11
|
+
|
12
|
+
## [v0.10.0](https://github.com/keypup-io/cloudtasker/tree/v0.10.0) (2020-09-02)
|
13
|
+
|
14
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.3...v0.10.0)
|
15
|
+
|
16
|
+
**Improvements:**
|
17
|
+
- Logging: Add worker name in log messages
|
18
|
+
- Logging: Add job duration in log messages
|
19
|
+
- Logging: Add Cloud Cloud Task ID in log messages
|
20
|
+
- Unique Job: Support TTL for lock keys. This feature prevents queues from being dead-locked when a critical crash occurs while processing a unique job.
|
21
|
+
- Worker: support payload storage in Redis instead of sending the payload to Google Cloud Tasks. This is useful when job arguments are expected to exceed 100kb, which is the limit set by Google Cloud Tasks
|
22
|
+
|
23
|
+
**Fixed bugs:**
|
24
|
+
- Local processing error: improve error handling and retries around network interruptions
|
25
|
+
- Redis client: prevent deadlocks in high concurrency scenario by slowing down poll time and enforcing lock expiration
|
26
|
+
- Redis client: use connecion pool with Redis to prevent race conditions
|
27
|
+
- Google API: improve error handling on job creation
|
28
|
+
- Google API: use the `X-CloudTasks-TaskRetryCount` instead of `X-CloudTasks-TaskExecutionCount` to detect how many retries Google Cloud Tasks has performed. Using `X-CloudTasks-TaskRetryCount` is theoretically less accurate than using `X-CloudTasks-TaskExecutionCount` because it includes the number of "app unreachable" retries but `X-CloudTasks-TaskExecutionCount` is currently bugged and remains at zero all the time. See [this issue](https://github.com/keypup-io/cloudtasker/issues/6)
|
29
|
+
|
30
|
+
## [v0.9.4](https://github.com/keypup-io/cloudtasker/tree/v0.9.4) (2020-10-05)
|
31
|
+
|
32
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.3...v0.9.4)
|
33
|
+
|
34
|
+
**Fixed bugs:**
|
35
|
+
- Logging: fix log processing with `semantic_logger` `v4.7.2`. Accept any args on block passed to the logger.
|
36
|
+
|
37
|
+
## [v0.9.3](https://github.com/keypup-io/cloudtasker/tree/v0.9.3) (2020-06-25)
|
38
|
+
|
39
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.2...v0.9.3)
|
40
|
+
|
41
|
+
**Fixed bugs:**
|
42
|
+
- Google Cloud Tasks: lock version to `~> 1.0` (Google recently released a v2 which changes its bindings completely). An [issue](https://github.com/keypup-io/cloudtasker/issues/11) has been raised to upgrade Cloudtasker to `google-cloud-tasks` `v2`.
|
43
|
+
|
3
44
|
## [v0.9.2](https://github.com/keypup-io/cloudtasker/tree/v0.9.2) (2020-03-04)
|
4
45
|
|
5
46
|
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.1...v0.9.2)
|
@@ -71,7 +112,3 @@ For Sinatra applications please update your Cloudtasker controller according to
|
|
71
112
|
## [v0.1.0](https://github.com/keypup-io/cloudtasker/tree/v0.1.0) (2019-11-17)
|
72
113
|
|
73
114
|
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/c137feb1ceaaaa4e2fecac0d1f0b4c73151ae002...v0.1.0)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/README.md
CHANGED
@@ -6,11 +6,11 @@ Background jobs for Ruby using Google Cloud Tasks.
|
|
6
6
|
|
7
7
|
Cloudtasker provides an easy to manage interface to Google Cloud Tasks for background job processing. Workers can be defined programmatically using the Cloudtasker DSL and enqueued for processing using a simple to use API.
|
8
8
|
|
9
|
-
Cloudtasker is particularly suited for serverless applications only responding to HTTP requests and where running a dedicated job processing 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.
|
9
|
+
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
10
|
|
11
11
|
Cloudtasker also provides optional modules for running [cron jobs](docs/CRON_JOBS.md), [batch jobs](docs/BATCH_JOBS.md) and [unique jobs](docs/UNIQUE_JOBS.md).
|
12
12
|
|
13
|
-
A local processing server is also available
|
13
|
+
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
14
|
|
15
15
|
## Summary
|
16
16
|
|
@@ -34,7 +34,11 @@ A local processing server is also available in development. This local server pr
|
|
34
34
|
1. [HTTP Error codes](#http-error-codes)
|
35
35
|
2. [Error callbacks](#error-callbacks)
|
36
36
|
3. [Max retries](#max-retries)
|
37
|
-
10. [
|
37
|
+
10. [Testing](#testing)
|
38
|
+
1. [Test helper setup](#test-helper-setup)
|
39
|
+
2. [In-memory queues](#in-memory-queues)
|
40
|
+
3. [Unit tests](#unit-tests)
|
41
|
+
11. [Best practices building workers](#best-practices-building-workers)
|
38
42
|
|
39
43
|
## Installation
|
40
44
|
|
@@ -48,7 +52,7 @@ And then execute:
|
|
48
52
|
|
49
53
|
$ bundle
|
50
54
|
|
51
|
-
Or install it yourself
|
55
|
+
Or install it yourself with:
|
52
56
|
|
53
57
|
$ gem install cloudtasker
|
54
58
|
|
@@ -71,7 +75,7 @@ Cloudtasker.configure do |config|
|
|
71
75
|
# Adapt the server port to be the one used by your Rails web process
|
72
76
|
#
|
73
77
|
config.processor_host = 'http://localhost:3000'
|
74
|
-
|
78
|
+
|
75
79
|
#
|
76
80
|
# If you do not have any Rails secret_key_base defined, uncomment the following
|
77
81
|
# This secret is used to authenticate jobs sent to the processing endpoint
|
@@ -154,14 +158,14 @@ The gem can be configured through an initializer. See below all the available co
|
|
154
158
|
Cloudtasker.configure do |config|
|
155
159
|
#
|
156
160
|
# If you do not have any Rails secret_key_base defined, uncomment the following.
|
157
|
-
# This secret is used to authenticate jobs sent to the processing endpoint
|
161
|
+
# This secret is used to authenticate jobs sent to the processing endpoint
|
158
162
|
# of your application.
|
159
163
|
#
|
160
164
|
# Default with Rails: Rails.application.credentials.secret_key_base
|
161
165
|
#
|
162
166
|
# config.secret = 'some-long-token'
|
163
167
|
|
164
|
-
#
|
168
|
+
#
|
165
169
|
# Specify the details of your Google Cloud Task location.
|
166
170
|
#
|
167
171
|
# This not required in development using the Cloudtasker local server.
|
@@ -185,21 +189,21 @@ Cloudtasker.configure do |config|
|
|
185
189
|
#
|
186
190
|
# Specific queues can be created in Cloud Tasks using the gcloud SDK or
|
187
191
|
# via the `rake cloudtasker:setup_queue name=<queue_name>` task.
|
188
|
-
#
|
192
|
+
#
|
189
193
|
config.gcp_queue_prefix = 'my-app'
|
190
194
|
|
191
|
-
#
|
195
|
+
#
|
192
196
|
# Specify the publicly accessible host for your application
|
193
197
|
#
|
194
198
|
# > E.g. in development, using the cloudtasker local server
|
195
199
|
# config.processor_host = 'http://localhost:3000'
|
196
|
-
#
|
200
|
+
#
|
197
201
|
# > E.g. in development, using `config.mode = :production` and ngrok
|
198
202
|
# config.processor_host = 'https://111111.ngrok.io'
|
199
203
|
#
|
200
204
|
config.processor_host = 'https://app.mydomain.com'
|
201
205
|
|
202
|
-
#
|
206
|
+
#
|
203
207
|
# Specify the mode of operation:
|
204
208
|
# - :development => jobs will be pushed to Redis and picked up by the Cloudtasker local server
|
205
209
|
# - :production => jobs will be pushed to Google Cloud Tasks. Requires a publicly accessible domain.
|
@@ -208,20 +212,20 @@ Cloudtasker.configure do |config|
|
|
208
212
|
#
|
209
213
|
# config.mode = Rails.env.production? || Rails.env.my_other_env? ? :production : :development
|
210
214
|
|
211
|
-
#
|
215
|
+
#
|
212
216
|
# Specify the logger to use
|
213
|
-
#
|
217
|
+
#
|
214
218
|
# Default with Rails: Rails.logger
|
215
219
|
# Default without Rails: Logger.new(STDOUT)
|
216
|
-
#
|
220
|
+
#
|
217
221
|
# config.logger = MyLogger.new(STDOUT)
|
218
222
|
|
219
|
-
#
|
223
|
+
#
|
220
224
|
# Specify how many retries are allowed on jobs. This number of retries excludes any
|
221
|
-
# connectivity error
|
222
|
-
#
|
225
|
+
# connectivity error due to the application being down or unreachable.
|
226
|
+
#
|
223
227
|
# Default: 25
|
224
|
-
#
|
228
|
+
#
|
225
229
|
# config.max_retries = 10
|
226
230
|
|
227
231
|
#
|
@@ -246,7 +250,7 @@ Cloudtasker.configure do |config|
|
|
246
250
|
# You can set this configuration parameter to a KB value if you want to store jobs
|
247
251
|
# args in redis only if the JSONified arguments payload exceeds that threshold.
|
248
252
|
#
|
249
|
-
# Supported since: v0.10.
|
253
|
+
# Supported since: v0.10.0
|
250
254
|
#
|
251
255
|
# Default: false
|
252
256
|
#
|
@@ -258,7 +262,7 @@ Cloudtasker.configure do |config|
|
|
258
262
|
end
|
259
263
|
```
|
260
264
|
|
261
|
-
If the default queue `<gcp_queue_prefix>-default` does not exist in Cloud Tasks you should [create it using the gcloud sdk](https://cloud.google.com/tasks/docs/creating-queues).
|
265
|
+
If the default queue `<gcp_queue_prefix>-default` does not exist in Cloud Tasks you should [create it using the gcloud sdk](https://cloud.google.com/tasks/docs/creating-queues).
|
262
266
|
|
263
267
|
Alternatively with Rails you can simply run the following rake task if you have queue admin permissions (`cloudtasks.queues.get` and `cloudtasks.queues.create`).
|
264
268
|
```bash
|
@@ -289,7 +293,7 @@ MyWorker.schedule(args: [arg1, arg2], time_at: Time.parse('2025-01-01 00:50:00Z'
|
|
289
293
|
MyWorker.schedule(args: [arg1, arg2], time_in: 5 * 60, queue: 'critical')
|
290
294
|
```
|
291
295
|
|
292
|
-
Cloudtasker also provides a helper for re-enqueuing jobs. Re-enqueued jobs keep the same
|
296
|
+
Cloudtasker also provides a helper for re-enqueuing jobs. Re-enqueued jobs keep the same job id. Some middlewares may rely on this to track the fact that that a job didn't actually complete (e.g. Cloustasker batch). This is optional and you can always fallback to using exception management (raise an error) to retry/re-enqueue jobs.
|
293
297
|
|
294
298
|
E.g.
|
295
299
|
```ruby
|
@@ -360,7 +364,7 @@ CriticalWorker.schedule(args: [1], queue: :important)
|
|
360
364
|
Cloudtasker comes with three optional features:
|
361
365
|
- Cron Jobs [[docs](docs/CRON_JOBS.md)]: Run jobs at fixed intervals.
|
362
366
|
- Batch Jobs [[docs](docs/BATCH_JOBS.md)]: Run jobs in jobs and track completion of the overall batch.
|
363
|
-
- Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
|
367
|
+
- Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
|
364
368
|
|
365
369
|
## Working locally
|
366
370
|
|
@@ -377,9 +381,9 @@ You can configure your application to use the Cloudtasker local server using the
|
|
377
381
|
|
378
382
|
Cloudtasker.configure do |config|
|
379
383
|
# ... other options
|
380
|
-
|
384
|
+
|
381
385
|
# Push jobs to redis and let the Cloudtasker local server collect them
|
382
|
-
# This is the default mode unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set
|
386
|
+
# This is the default mode unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set
|
383
387
|
# to a non-development environment
|
384
388
|
config.mode = :development
|
385
389
|
end
|
@@ -423,7 +427,7 @@ Cloudtasker.configure do |config|
|
|
423
427
|
|
424
428
|
# Use your ngrok domain as the processor host
|
425
429
|
config.processor_host = 'https://your-tunnel-id.ngrok.io'
|
426
|
-
|
430
|
+
|
427
431
|
# Force Cloudtasker to use Google Cloud Tasks in development
|
428
432
|
config.mode = :production
|
429
433
|
end
|
@@ -474,7 +478,7 @@ The way contextual information is displayed depends on the logger itself. For ex
|
|
474
478
|
|
475
479
|
Contextual information can be customised globally and locally using a log context_processor. By default the `Cloudtasker::WorkerLogger` is configured the following way:
|
476
480
|
```ruby
|
477
|
-
Cloudtasker::WorkerLogger.log_context_processor = ->(worker) { worker.to_h.slice(:worker, :job_id, :job_meta) }
|
481
|
+
Cloudtasker::WorkerLogger.log_context_processor = ->(worker) { worker.to_h.slice(:worker, :job_id, :job_meta, :job_queue, :task_id) }
|
478
482
|
```
|
479
483
|
|
480
484
|
You can decide to add a global identifier for your worker logs using the following:
|
@@ -482,7 +486,7 @@ You can decide to add a global identifier for your worker logs using the followi
|
|
482
486
|
# config/initializers/cloudtasker.rb
|
483
487
|
|
484
488
|
Cloudtasker::WorkerLogger.log_context_processor = lambda { |worker|
|
485
|
-
worker.to_h.slice(:worker, :job_id, :job_meta).merge(app: 'my-app')
|
489
|
+
worker.to_h.slice(:worker, :job_id, :job_meta, :job_queue, :task_id).merge(app: 'my-app')
|
486
490
|
}
|
487
491
|
```
|
488
492
|
|
@@ -504,7 +508,7 @@ end
|
|
504
508
|
See the [Cloudtasker::Worker class](lib/cloudtasker/worker.rb) for more information on attributes available to be logged in your `log_context_processor` proc.
|
505
509
|
|
506
510
|
### Searching logs: Job ID vs Task ID
|
507
|
-
**Note**: `task_id` field is available in logs starting with `0.10.
|
511
|
+
**Note**: `task_id` field is available in logs starting with `0.10.0`
|
508
512
|
|
509
513
|
Job instances are assigned two different different IDs for tracking and logging purpose: `job_id` and `task_id`. These IDs are found in each log entry to facilitate search.
|
510
514
|
|
@@ -520,7 +524,7 @@ The Google Cloud Task UI (GCP console) lists all the tasks pending/retrying and
|
|
520
524
|
|
521
525
|
## Error Handling
|
522
526
|
|
523
|
-
Jobs
|
527
|
+
Jobs failures will return an HTTP error to Cloud Task and trigger a retry at a later time. The number of Cloud Task retries depends on the configuration of your queue in Cloud Tasks.
|
524
528
|
|
525
529
|
### HTTP Error codes
|
526
530
|
|
@@ -528,6 +532,7 @@ Jobs failing will automatically return the following HTTP error code to Cloud Ta
|
|
528
532
|
|
529
533
|
| Code | Description |
|
530
534
|
|------|-------------|
|
535
|
+
| 204 | The job was processed successfully |
|
531
536
|
| 205 | The job is dead and has been removed from the queue |
|
532
537
|
| 404 | The job has specified an incorrect worker class. |
|
533
538
|
| 422 | An error happened during the execution of the worker (`perform` method) |
|
@@ -566,26 +571,24 @@ By default jobs are retried 25 times - using an exponential backoff - before bei
|
|
566
571
|
|
567
572
|
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.
|
568
573
|
|
569
|
-
**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 `
|
574
|
+
**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 `v0.10.0` 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.
|
570
575
|
|
571
576
|
E.g. Set max number of retries globally via the cloudtasker initializer.
|
572
577
|
```ruby
|
573
578
|
# config/initializers/cloudtasker.rb
|
574
579
|
|
575
580
|
Cloudtasker.configure do |config|
|
576
|
-
#
|
581
|
+
#
|
577
582
|
# Specify how many retries are allowed on jobs. This number of retries excludes any
|
578
583
|
# connectivity error that would be due to the application being down or unreachable.
|
579
|
-
#
|
584
|
+
#
|
580
585
|
# Default: 25
|
581
|
-
#
|
586
|
+
#
|
582
587
|
config.max_retries = 10
|
583
588
|
end
|
584
589
|
```
|
585
590
|
|
586
591
|
E.g. Set max number of retries to 3 on a given worker
|
587
|
-
|
588
|
-
E.g.
|
589
592
|
```ruby
|
590
593
|
# app/workers/some_error_worker.rb
|
591
594
|
|
@@ -595,12 +598,126 @@ class SomeErrorWorker
|
|
595
598
|
# This will override the global setting
|
596
599
|
cloudtasker_options max_retries: 3
|
597
600
|
|
598
|
-
def perform
|
601
|
+
def perform
|
599
602
|
raise(ArgumentError)
|
600
603
|
end
|
601
604
|
end
|
602
605
|
```
|
603
606
|
|
607
|
+
E.g. Evaluate the number of max retries at runtime (Supported since: v0.10.1)
|
608
|
+
```ruby
|
609
|
+
# app/workers/some_error_worker.rb
|
610
|
+
|
611
|
+
class SomeErrorWorker
|
612
|
+
include Cloudtasker::Worker
|
613
|
+
|
614
|
+
# Return the number of max retries based on
|
615
|
+
# worker arguments.
|
616
|
+
#
|
617
|
+
# If this method returns nil then max_retries
|
618
|
+
# will delegate to the class `max_retries` setting or Cloudtasker
|
619
|
+
# `max_retries` configuration otion.
|
620
|
+
def max_retries(arg1, arg2)
|
621
|
+
arg1 == 'foo' ? 13 : nil
|
622
|
+
end
|
623
|
+
|
624
|
+
def perform(arg1, arg2)
|
625
|
+
raise(ArgumentError)
|
626
|
+
end
|
627
|
+
end
|
628
|
+
```
|
629
|
+
|
630
|
+
## Testing
|
631
|
+
Cloudtasker provides several options to test your workers.
|
632
|
+
|
633
|
+
### Test helper setup
|
634
|
+
Require `cloudtasker/testing` in your `rails_helper.rb` (Rspec Rails) or `spec_helper.rb` (Rspec) or test unit helper file then enable one of the three modes:
|
635
|
+
|
636
|
+
```ruby
|
637
|
+
require 'cloudtasker/testing'
|
638
|
+
|
639
|
+
# Mode 1 (default): Push jobs to Google Cloud Tasks (env != development) or Redis (env == development)
|
640
|
+
Cloudtasker::Testing.enable!
|
641
|
+
|
642
|
+
# Mode 2: Push jobs to an in-memory queue. Jobs will not be processed until you call
|
643
|
+
# Cloudtasker::Worker.drain_all (process all jobs) or MyWorker.drain (process jobs for specific worker)
|
644
|
+
Cloudtasker::Testing.fake!
|
645
|
+
|
646
|
+
# Mode 3: Push jobs to an in-memory queue. Jobs will be processed immediately.
|
647
|
+
Cloudtasker::Testing.inline!
|
648
|
+
```
|
649
|
+
|
650
|
+
You can query the current testing mode with:
|
651
|
+
```ruby
|
652
|
+
Cloudtasker::Testing.enabled?
|
653
|
+
Cloudtasker::Testing.fake?
|
654
|
+
Cloudtasker::Testing.inline?
|
655
|
+
```
|
656
|
+
|
657
|
+
Each testing mode accepts a block argument to temporarily switch to it:
|
658
|
+
```ruby
|
659
|
+
# Enable fake mode for all tests
|
660
|
+
Cloudtasker::Testing.fake!
|
661
|
+
|
662
|
+
# Enable inline! mode temporarily for a given test
|
663
|
+
Cloudtasker.inline! do
|
664
|
+
MyWorker.perform_async(1,2)
|
665
|
+
end
|
666
|
+
```
|
667
|
+
|
668
|
+
Note that extension middlewares - e.g. unique job, batch job etc. - run in test mode. You can disable middlewares in your tests by adding the following to your test helper:
|
669
|
+
```ruby
|
670
|
+
# Remove all middlewares
|
671
|
+
Cloudtasker.configure do |c|
|
672
|
+
c.client_middleware.clear
|
673
|
+
c.server_middleware.clear
|
674
|
+
end
|
675
|
+
|
676
|
+
# Remove all unique job middlewares
|
677
|
+
Cloudtasker.configure do |c|
|
678
|
+
c.client_middleware.remove(Cloudtasker::UniqueJob::Middleware::Client)
|
679
|
+
c.server_middleware.remove(Cloudtasker::UniqueJob::Middleware::Server)
|
680
|
+
end
|
681
|
+
```
|
682
|
+
|
683
|
+
### In-memory queues
|
684
|
+
The `fake!` or `inline!` modes use in-memory queues, which can be queried and controlled using the following methods:
|
685
|
+
|
686
|
+
```ruby
|
687
|
+
# Perform all jobs in queue
|
688
|
+
Cloudtasker::Worker.drain_all
|
689
|
+
|
690
|
+
# Remove all jobs in queue
|
691
|
+
Cloudtasker::Worker.clear_all
|
692
|
+
|
693
|
+
# Perform all jobs in queue for a specific worker type
|
694
|
+
MyWorker.drain
|
695
|
+
|
696
|
+
# Return the list of jobs in queue for a specific worker type
|
697
|
+
MyWorker.jobs
|
698
|
+
```
|
699
|
+
|
700
|
+
### Unit tests
|
701
|
+
Below are examples of rspec tests. It is assumed that `Cloudtasker::Testing.fake!` has been set in the test helper.
|
702
|
+
|
703
|
+
**Example 1**: Testing that a job is scheduled
|
704
|
+
```ruby
|
705
|
+
describe 'worker scheduling'
|
706
|
+
subject(:enqueue_job) { MyWorker.perform_async(1,2) }
|
707
|
+
|
708
|
+
it { expect { enqueue_job }.to change(MyWorker.jobs, :size).by(1) }
|
709
|
+
end
|
710
|
+
```
|
711
|
+
|
712
|
+
**Example 2**: Testing job execution logic
|
713
|
+
```ruby
|
714
|
+
describe 'worker calls api'
|
715
|
+
subject { Cloudtasker::Testing.inline! { MyApiWorker.perform_async(1,2) } }
|
716
|
+
|
717
|
+
before { expect(MyApi).to receive(:fetch).and_return([]) }
|
718
|
+
it { is_expected.to be_truthy }
|
719
|
+
end
|
720
|
+
```
|
604
721
|
|
605
722
|
## Best practices building workers
|
606
723
|
|
@@ -676,7 +793,7 @@ Google Cloud Tasks enforces a limit of 100 KB for job payloads. Taking into acco
|
|
676
793
|
Any excessive job payload (> 100 KB) will raise a `Cloudtasker::MaxTaskSizeExceededError`, both in production and development mode.
|
677
794
|
|
678
795
|
#### Option 1: Use Cloudtasker optional support for payload storage in Redis
|
679
|
-
**Supported since**: `0.10.
|
796
|
+
**Supported since**: `0.10.0`
|
680
797
|
|
681
798
|
Cloudtasker provides optional support for storing argument payloads in Redis instead of sending them to Google Cloud Tasks.
|
682
799
|
|
@@ -19,21 +19,32 @@ module Cloudtasker
|
|
19
19
|
# Process payload
|
20
20
|
WorkerHandler.execute_from_payload!(payload)
|
21
21
|
head :no_content
|
22
|
-
rescue DeadWorkerError
|
22
|
+
rescue DeadWorkerError, MissingWorkerArgumentsError => e
|
23
23
|
# 205: job will NOT be retried
|
24
|
+
log_error(e)
|
24
25
|
head :reset_content
|
25
|
-
rescue InvalidWorkerError
|
26
|
+
rescue InvalidWorkerError => e
|
26
27
|
# 404: Job will be retried
|
28
|
+
log_error(e)
|
27
29
|
head :not_found
|
28
30
|
rescue StandardError => e
|
29
31
|
# 404: Job will be retried
|
30
|
-
|
31
|
-
Cloudtasker.logger.error(e.backtrace.join("\n"))
|
32
|
+
log_error(e)
|
32
33
|
head :unprocessable_entity
|
33
34
|
end
|
34
35
|
|
35
36
|
private
|
36
37
|
|
38
|
+
#
|
39
|
+
# Log an error via cloudtasker logger.
|
40
|
+
#
|
41
|
+
# @param [Exception] e The error to log
|
42
|
+
#
|
43
|
+
def log_error(error)
|
44
|
+
Cloudtasker.logger.error(error)
|
45
|
+
Cloudtasker.logger.error(error.backtrace.join("\n"))
|
46
|
+
end
|
47
|
+
|
37
48
|
#
|
38
49
|
# Parse the request body and return the actual job
|
39
50
|
# payload.
|
data/cloudtasker.gemspec
CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_dependency 'activesupport'
|
32
32
|
spec.add_dependency 'connection_pool'
|
33
33
|
spec.add_dependency 'fugit'
|
34
|
-
spec.add_dependency 'google-cloud-tasks'
|
34
|
+
spec.add_dependency 'google-cloud-tasks', '~> 1.0'
|
35
35
|
spec.add_dependency 'jwt'
|
36
36
|
spec.add_dependency 'redis'
|
37
37
|
spec.add_dependency 'retriable'
|
@@ -43,6 +43,7 @@ Gem::Specification.new do |spec|
|
|
43
43
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
44
|
spec.add_development_dependency 'rubocop', '0.76.0'
|
45
45
|
spec.add_development_dependency 'rubocop-rspec', '1.37.0'
|
46
|
+
spec.add_development_dependency 'semantic_logger'
|
46
47
|
spec.add_development_dependency 'timecop'
|
47
48
|
spec.add_development_dependency 'webmock'
|
48
49
|
|
data/lib/cloudtasker.rb
CHANGED
@@ -8,6 +8,7 @@ require 'cloudtasker/config'
|
|
8
8
|
require 'cloudtasker/authentication_error'
|
9
9
|
require 'cloudtasker/dead_worker_error'
|
10
10
|
require 'cloudtasker/invalid_worker_error'
|
11
|
+
require 'cloudtasker/missing_worker_arguments_error'
|
11
12
|
require 'cloudtasker/max_task_size_exceeded_error'
|
12
13
|
|
13
14
|
require 'cloudtasker/middleware/chain'
|
@@ -8,6 +8,15 @@ module Cloudtasker
|
|
8
8
|
attr_accessor :job_retries
|
9
9
|
attr_reader :id, :http_request, :schedule_time, :queue
|
10
10
|
|
11
|
+
#
|
12
|
+
# Return true if we are in test inline execution mode.
|
13
|
+
#
|
14
|
+
# @return [Boolean] True if inline mode enabled.
|
15
|
+
#
|
16
|
+
def self.inline_mode?
|
17
|
+
defined?(Cloudtasker::Testing) && Cloudtasker::Testing.inline?
|
18
|
+
end
|
19
|
+
|
11
20
|
#
|
12
21
|
# Return the task queue. A worker class name
|
13
22
|
#
|
@@ -57,7 +66,7 @@ module Cloudtasker
|
|
57
66
|
queue << task
|
58
67
|
|
59
68
|
# Execute task immediately if in testing and inline mode enabled
|
60
|
-
task.execute if
|
69
|
+
task.execute if inline_mode?
|
61
70
|
|
62
71
|
task
|
63
72
|
end
|
@@ -157,8 +166,12 @@ module Cloudtasker
|
|
157
166
|
# Delete task
|
158
167
|
self.class.delete(id)
|
159
168
|
resp
|
160
|
-
rescue
|
169
|
+
rescue DeadWorkerError => e
|
170
|
+
self.class.delete(id)
|
171
|
+
raise(e) if self.class.inline_mode?
|
172
|
+
rescue StandardError => e
|
161
173
|
self.job_retries += 1
|
174
|
+
raise(e) if self.class.inline_mode?
|
162
175
|
end
|
163
176
|
|
164
177
|
#
|
data/lib/cloudtasker/version.rb
CHANGED
data/lib/cloudtasker/worker.rb
CHANGED
@@ -296,6 +296,20 @@ module Cloudtasker
|
|
296
296
|
other.is_a?(self.class) && other.job_id == job_id
|
297
297
|
end
|
298
298
|
|
299
|
+
#
|
300
|
+
# Return the max number of retries allowed for this job.
|
301
|
+
#
|
302
|
+
# The order of precedence for retry lookup is:
|
303
|
+
# - Worker `max_retries` method
|
304
|
+
# - Class `max_retries` option
|
305
|
+
# - Cloudtasker `max_retries` config option
|
306
|
+
#
|
307
|
+
# @return [Integer] The number of retries
|
308
|
+
#
|
309
|
+
def job_max_retries
|
310
|
+
@job_max_retries ||= (try(:max_retries, *job_args) || self.class.max_retries)
|
311
|
+
end
|
312
|
+
|
299
313
|
#
|
300
314
|
# Return true if the job has excceeded its maximum number
|
301
315
|
# of retries
|
@@ -303,7 +317,7 @@ module Cloudtasker
|
|
303
317
|
# @return [Boolean] True if the job is dead
|
304
318
|
#
|
305
319
|
def job_dead?
|
306
|
-
job_retries >=
|
320
|
+
job_retries >= job_max_retries
|
307
321
|
end
|
308
322
|
|
309
323
|
#
|
@@ -333,6 +347,13 @@ module Cloudtasker
|
|
333
347
|
|
334
348
|
Cloudtasker.config.server_middleware.invoke(self) do
|
335
349
|
begin
|
350
|
+
# Abort if arguments are missing. This may happen with redis arguments storage
|
351
|
+
# if Cloud Tasks times out on a job but the job still succeeds
|
352
|
+
if job_args.empty? && [0, -1].exclude?(method(:perform).arity)
|
353
|
+
raise(MissingWorkerArgumentsError, 'worker arguments are missing')
|
354
|
+
end
|
355
|
+
|
356
|
+
# Perform the job
|
336
357
|
perform(*job_args)
|
337
358
|
rescue StandardError => e
|
338
359
|
try(:on_error, e)
|
@@ -85,7 +85,7 @@ module Cloudtasker
|
|
85
85
|
redis.expire(args_payload_key, ARGS_PAYLOAD_CLEANUP_TTL) if args_payload_key && !worker.job_reenqueued
|
86
86
|
|
87
87
|
resp
|
88
|
-
rescue DeadWorkerError => e
|
88
|
+
rescue DeadWorkerError, MissingWorkerArgumentsError => e
|
89
89
|
# Delete stored args payload if job is dead
|
90
90
|
redis.expire(args_payload_key, ARGS_PAYLOAD_CLEANUP_TTL) if args_payload_key
|
91
91
|
raise(e)
|
@@ -142,7 +142,7 @@ module Cloudtasker
|
|
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
|
+
payload_block = ->(*_args) { log_block.call.merge(block&.call || {}) }
|
146
146
|
|
147
147
|
# ActiveSupport::Logger does not support passing a payload through a block on top
|
148
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.
|
4
|
+
version: 0.11.rc1
|
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-
|
11
|
+
date: 2020-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -56,16 +56,16 @@ dependencies:
|
|
56
56
|
name: google-cloud-tasks
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
61
|
+
version: '1.0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
68
|
+
version: '1.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: jwt
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +206,20 @@ dependencies:
|
|
206
206
|
- - '='
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: 1.37.0
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: semantic_logger
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
209
223
|
- !ruby/object:Gem::Dependency
|
210
224
|
name: timecop
|
211
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -324,6 +338,11 @@ files:
|
|
324
338
|
- gemfiles/rails_5.2.gemfile.lock
|
325
339
|
- gemfiles/rails_6.0.gemfile
|
326
340
|
- gemfiles/rails_6.0.gemfile.lock
|
341
|
+
- gemfiles/semantic_logger_3.4.gemfile
|
342
|
+
- gemfiles/semantic_logger_4.6.gemfile
|
343
|
+
- gemfiles/semantic_logger_4.7.0.gemfile
|
344
|
+
- gemfiles/semantic_logger_4.7.2.gemfile
|
345
|
+
- gemfiles/semantic_logger_4.7.gemfile
|
327
346
|
- lib/cloudtasker.rb
|
328
347
|
- lib/cloudtasker/authentication_error.rb
|
329
348
|
- lib/cloudtasker/authenticator.rb
|
@@ -351,6 +370,7 @@ files:
|
|
351
370
|
- lib/cloudtasker/max_task_size_exceeded_error.rb
|
352
371
|
- lib/cloudtasker/meta_store.rb
|
353
372
|
- lib/cloudtasker/middleware/chain.rb
|
373
|
+
- lib/cloudtasker/missing_worker_arguments_error.rb
|
354
374
|
- lib/cloudtasker/redis_client.rb
|
355
375
|
- lib/cloudtasker/testing.rb
|
356
376
|
- lib/cloudtasker/unique_job.rb
|