cloudtasker 0.10.rc7 → 0.11.rc2
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 -0
- data/Appraisals +16 -0
- data/CHANGELOG.md +49 -4
- data/README.md +146 -33
- data/app/controllers/cloudtasker/worker_controller.rb +15 -4
- data/cloudtasker.gemspec +3 -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/active_job/queue_adapters/cloudtasker_adapter.rb +82 -0
- data/lib/cloudtasker.rb +1 -0
- data/lib/cloudtasker/backend/memory_task.rb +3 -0
- data/lib/cloudtasker/cli.rb +1 -0
- data/lib/cloudtasker/engine.rb +6 -0
- 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 +41 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5d91132bd3363bf3d7c08cd207b385f502eae3056be4734f73f60ef220f94c5
|
4
|
+
data.tar.gz: fe935947d1ece340202d32eb54927336840ca236510a5044c2b1996c2161080d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed918a89e19dca6068f6c4d7b2aa0e55f838829f19741b1509edcbf3e03117e102454791efd808c5521da30e862d8cfc1d5969444cfb6ee250589e5b7dd281de
|
7
|
+
data.tar.gz: d4fbb9afebcf84efb8bbe3f11b126aefc93a14c62d9d09b1a2f27186defb9c30ee0d28c13688b6bcc38411a3cbe8a736010fbea1a75e7dfa6334870e0522f2ff
|
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,54 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Latest RC: [v0.11.rc2](https://github.com/keypup-io/cloudtasker/tree/v0.11.rc2) (2020-11-23)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.10.0...v0.11.rc2)
|
6
|
+
|
7
|
+
**Improvements:**
|
8
|
+
- Worker: drop job (return 205 response) when worker arguments are not available (e.g. arguments were stored in Redis and the latter was flushed)
|
9
|
+
- Rails: add ActiveJob adapter (thanks @vovimayhem)
|
10
|
+
|
11
|
+
## [v0.10.1](https://github.com/keypup-io/cloudtasker/tree/v0.10.1) (2020-10-05)
|
12
|
+
|
13
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.10.0...v0.10.1)
|
14
|
+
|
15
|
+
**Fixed bugs:**
|
16
|
+
- Local server: delete dead task from local server queue
|
17
|
+
- Logging: fix log processing with `semantic_logger` `v4.7.2`. Accept any args on block passed to the logger.
|
18
|
+
- Worker: fix configuration of `max_retries` at worker level
|
19
|
+
|
20
|
+
## [v0.10.0](https://github.com/keypup-io/cloudtasker/tree/v0.10.0) (2020-09-02)
|
21
|
+
|
22
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.3...v0.10.0)
|
23
|
+
|
24
|
+
**Improvements:**
|
25
|
+
- Logging: Add worker name in log messages
|
26
|
+
- Logging: Add job duration in log messages
|
27
|
+
- Logging: Add Cloud Cloud Task ID in log messages
|
28
|
+
- 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.
|
29
|
+
- 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
|
30
|
+
|
31
|
+
**Fixed bugs:**
|
32
|
+
- Local processing error: improve error handling and retries around network interruptions
|
33
|
+
- Redis client: prevent deadlocks in high concurrency scenario by slowing down poll time and enforcing lock expiration
|
34
|
+
- Redis client: use connecion pool with Redis to prevent race conditions
|
35
|
+
- Google API: improve error handling on job creation
|
36
|
+
- 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)
|
37
|
+
|
38
|
+
## [v0.9.4](https://github.com/keypup-io/cloudtasker/tree/v0.9.4) (2020-10-05)
|
39
|
+
|
40
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.3...v0.9.4)
|
41
|
+
|
42
|
+
**Fixed bugs:**
|
43
|
+
- Logging: fix log processing with `semantic_logger` `v4.7.2`. Accept any args on block passed to the logger.
|
44
|
+
|
45
|
+
## [v0.9.3](https://github.com/keypup-io/cloudtasker/tree/v0.9.3) (2020-06-25)
|
46
|
+
|
47
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.2...v0.9.3)
|
48
|
+
|
49
|
+
**Fixed bugs:**
|
50
|
+
- 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`.
|
51
|
+
|
3
52
|
## [v0.9.2](https://github.com/keypup-io/cloudtasker/tree/v0.9.2) (2020-03-04)
|
4
53
|
|
5
54
|
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.1...v0.9.2)
|
@@ -71,7 +120,3 @@ For Sinatra applications please update your Cloudtasker controller according to
|
|
71
120
|
## [v0.1.0](https://github.com/keypup-io/cloudtasker/tree/v0.1.0) (2019-11-17)
|
72
121
|
|
73
122
|
[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
@@ -16,6 +16,7 @@ A local processing server is also available for development. This local server p
|
|
16
16
|
|
17
17
|
1. [Installation](#installation)
|
18
18
|
2. [Get started with Rails](#get-started-with-rails)
|
19
|
+
2. [Get started with Rails & ActiveJob](#get-started-with-rails--activejob)
|
19
20
|
3. [Configuring Cloudtasker](#configuring-cloudtasker)
|
20
21
|
1. [Cloud Tasks authentication & permissions](#cloud-tasks-authentication--permissions)
|
21
22
|
2. [Cloudtasker initializer](#cloudtasker-initializer)
|
@@ -75,7 +76,7 @@ Cloudtasker.configure do |config|
|
|
75
76
|
# Adapt the server port to be the one used by your Rails web process
|
76
77
|
#
|
77
78
|
config.processor_host = 'http://localhost:3000'
|
78
|
-
|
79
|
+
|
79
80
|
#
|
80
81
|
# If you do not have any Rails secret_key_base defined, uncomment the following
|
81
82
|
# This secret is used to authenticate jobs sent to the processing endpoint
|
@@ -132,6 +133,95 @@ That's it! Your job was picked up by the Cloudtasker local server and sent for p
|
|
132
133
|
|
133
134
|
Now jump to the next section to configure your app to use Google Cloud Tasks as a backend.
|
134
135
|
|
136
|
+
## Get started with Rails & ActiveJob
|
137
|
+
**Note**: ActiveJob is supported since `0.11.rc2`
|
138
|
+
**Note**: Cloudtasker extensions (cron, batch and unique jobs) are not available when using cloudtasker via ActiveJob.
|
139
|
+
|
140
|
+
Cloudtasker is pre-integrated with ActiveJob. Follow the steps below to get started.
|
141
|
+
|
142
|
+
Install redis on your machine (this is required by the Cloudtasker local processing server)
|
143
|
+
```bash
|
144
|
+
# E.g. using brew
|
145
|
+
brew install redis
|
146
|
+
```
|
147
|
+
|
148
|
+
Add the following initializer
|
149
|
+
```ruby
|
150
|
+
# config/initializers/cloudtasker.rb
|
151
|
+
|
152
|
+
Cloudtasker.configure do |config|
|
153
|
+
#
|
154
|
+
# Adapt the server port to be the one used by your Rails web process
|
155
|
+
#
|
156
|
+
config.processor_host = 'http://localhost:3000'
|
157
|
+
|
158
|
+
#
|
159
|
+
# If you do not have any Rails secret_key_base defined, uncomment the following
|
160
|
+
# This secret is used to authenticate jobs sent to the processing endpoint
|
161
|
+
# of your application.
|
162
|
+
#
|
163
|
+
# config.secret = 'some-long-token'
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
Configure ActiveJob to use Cloudtasker. You can also configure ActiveJob per environment via the config/environments/:env.rb files
|
168
|
+
```ruby
|
169
|
+
# config/application.rb
|
170
|
+
|
171
|
+
require_relative 'boot'
|
172
|
+
require 'rails/all'
|
173
|
+
|
174
|
+
Bundler.require(*Rails.groups)
|
175
|
+
|
176
|
+
module Dummy
|
177
|
+
class Application < Rails::Application
|
178
|
+
# Initialize configuration defaults for originally generated Rails version.
|
179
|
+
config.load_defaults 6.0
|
180
|
+
|
181
|
+
# Settings in config/environments/* take precedence over those specified here.
|
182
|
+
# Application configuration can go into files in config/initializers
|
183
|
+
# -- all .rb files in that directory are automatically loaded after loading
|
184
|
+
# the framework and any gems in your application.
|
185
|
+
|
186
|
+
# Use cloudtasker as the ActiveJob backend:
|
187
|
+
config.active_job.queue_adapter = :cloudtasker
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
```
|
192
|
+
|
193
|
+
Define your first job:
|
194
|
+
```ruby
|
195
|
+
# app/jobs/example_job.rb
|
196
|
+
|
197
|
+
class ExampleJob < ApplicationJob
|
198
|
+
queue_as :default
|
199
|
+
|
200
|
+
def perform(some_arg)
|
201
|
+
logger.info("Job run with #{some_arg}. This is working!")
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
Launch Rails and the local Cloudtasker processing server (or add `cloudtasker` to your foreman config as a `worker` process)
|
207
|
+
```bash
|
208
|
+
# In one terminal
|
209
|
+
> rails s -p 3000
|
210
|
+
|
211
|
+
# In another terminal
|
212
|
+
> cloudtasker
|
213
|
+
```
|
214
|
+
|
215
|
+
Open a Rails console and enqueue some jobs
|
216
|
+
```ruby
|
217
|
+
# Process job as soon as possible
|
218
|
+
ExampleJob.perform_later('foo')
|
219
|
+
|
220
|
+
# Process job in 60 seconds
|
221
|
+
ExampleJob.set(wait: 60).perform_later('foo')
|
222
|
+
```
|
223
|
+
|
224
|
+
|
135
225
|
## Configuring Cloudtasker
|
136
226
|
|
137
227
|
### Cloud Tasks authentication & permissions
|
@@ -158,14 +248,14 @@ The gem can be configured through an initializer. See below all the available co
|
|
158
248
|
Cloudtasker.configure do |config|
|
159
249
|
#
|
160
250
|
# If you do not have any Rails secret_key_base defined, uncomment the following.
|
161
|
-
# This secret is used to authenticate jobs sent to the processing endpoint
|
251
|
+
# This secret is used to authenticate jobs sent to the processing endpoint
|
162
252
|
# of your application.
|
163
253
|
#
|
164
254
|
# Default with Rails: Rails.application.credentials.secret_key_base
|
165
255
|
#
|
166
256
|
# config.secret = 'some-long-token'
|
167
257
|
|
168
|
-
#
|
258
|
+
#
|
169
259
|
# Specify the details of your Google Cloud Task location.
|
170
260
|
#
|
171
261
|
# This not required in development using the Cloudtasker local server.
|
@@ -189,21 +279,21 @@ Cloudtasker.configure do |config|
|
|
189
279
|
#
|
190
280
|
# Specific queues can be created in Cloud Tasks using the gcloud SDK or
|
191
281
|
# via the `rake cloudtasker:setup_queue name=<queue_name>` task.
|
192
|
-
#
|
282
|
+
#
|
193
283
|
config.gcp_queue_prefix = 'my-app'
|
194
284
|
|
195
|
-
#
|
285
|
+
#
|
196
286
|
# Specify the publicly accessible host for your application
|
197
287
|
#
|
198
288
|
# > E.g. in development, using the cloudtasker local server
|
199
289
|
# config.processor_host = 'http://localhost:3000'
|
200
|
-
#
|
290
|
+
#
|
201
291
|
# > E.g. in development, using `config.mode = :production` and ngrok
|
202
292
|
# config.processor_host = 'https://111111.ngrok.io'
|
203
293
|
#
|
204
294
|
config.processor_host = 'https://app.mydomain.com'
|
205
295
|
|
206
|
-
#
|
296
|
+
#
|
207
297
|
# Specify the mode of operation:
|
208
298
|
# - :development => jobs will be pushed to Redis and picked up by the Cloudtasker local server
|
209
299
|
# - :production => jobs will be pushed to Google Cloud Tasks. Requires a publicly accessible domain.
|
@@ -212,20 +302,20 @@ Cloudtasker.configure do |config|
|
|
212
302
|
#
|
213
303
|
# config.mode = Rails.env.production? || Rails.env.my_other_env? ? :production : :development
|
214
304
|
|
215
|
-
#
|
305
|
+
#
|
216
306
|
# Specify the logger to use
|
217
|
-
#
|
307
|
+
#
|
218
308
|
# Default with Rails: Rails.logger
|
219
309
|
# Default without Rails: Logger.new(STDOUT)
|
220
|
-
#
|
310
|
+
#
|
221
311
|
# config.logger = MyLogger.new(STDOUT)
|
222
312
|
|
223
|
-
#
|
313
|
+
#
|
224
314
|
# Specify how many retries are allowed on jobs. This number of retries excludes any
|
225
315
|
# connectivity error due to the application being down or unreachable.
|
226
|
-
#
|
316
|
+
#
|
227
317
|
# Default: 25
|
228
|
-
#
|
318
|
+
#
|
229
319
|
# config.max_retries = 10
|
230
320
|
|
231
321
|
#
|
@@ -250,7 +340,7 @@ Cloudtasker.configure do |config|
|
|
250
340
|
# You can set this configuration parameter to a KB value if you want to store jobs
|
251
341
|
# args in redis only if the JSONified arguments payload exceeds that threshold.
|
252
342
|
#
|
253
|
-
# Supported since: v0.10.
|
343
|
+
# Supported since: v0.10.0
|
254
344
|
#
|
255
345
|
# Default: false
|
256
346
|
#
|
@@ -262,7 +352,7 @@ Cloudtasker.configure do |config|
|
|
262
352
|
end
|
263
353
|
```
|
264
354
|
|
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).
|
355
|
+
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).
|
266
356
|
|
267
357
|
Alternatively with Rails you can simply run the following rake task if you have queue admin permissions (`cloudtasks.queues.get` and `cloudtasks.queues.create`).
|
268
358
|
```bash
|
@@ -361,10 +451,12 @@ CriticalWorker.schedule(args: [1], queue: :important)
|
|
361
451
|
```
|
362
452
|
|
363
453
|
## Extensions
|
454
|
+
**Note**: Extensions are not available when using cloudtasker via ActiveJob.
|
455
|
+
|
364
456
|
Cloudtasker comes with three optional features:
|
365
457
|
- Cron Jobs [[docs](docs/CRON_JOBS.md)]: Run jobs at fixed intervals.
|
366
458
|
- Batch Jobs [[docs](docs/BATCH_JOBS.md)]: Run jobs in jobs and track completion of the overall batch.
|
367
|
-
- Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
|
459
|
+
- Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
|
368
460
|
|
369
461
|
## Working locally
|
370
462
|
|
@@ -381,9 +473,9 @@ You can configure your application to use the Cloudtasker local server using the
|
|
381
473
|
|
382
474
|
Cloudtasker.configure do |config|
|
383
475
|
# ... other options
|
384
|
-
|
476
|
+
|
385
477
|
# Push jobs to redis and let the Cloudtasker local server collect them
|
386
|
-
# This is the default mode unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set
|
478
|
+
# This is the default mode unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set
|
387
479
|
# to a non-development environment
|
388
480
|
config.mode = :development
|
389
481
|
end
|
@@ -427,7 +519,7 @@ Cloudtasker.configure do |config|
|
|
427
519
|
|
428
520
|
# Use your ngrok domain as the processor host
|
429
521
|
config.processor_host = 'https://your-tunnel-id.ngrok.io'
|
430
|
-
|
522
|
+
|
431
523
|
# Force Cloudtasker to use Google Cloud Tasks in development
|
432
524
|
config.mode = :production
|
433
525
|
end
|
@@ -508,7 +600,7 @@ end
|
|
508
600
|
See the [Cloudtasker::Worker class](lib/cloudtasker/worker.rb) for more information on attributes available to be logged in your `log_context_processor` proc.
|
509
601
|
|
510
602
|
### Searching logs: Job ID vs Task ID
|
511
|
-
**Note**: `task_id` field is available in logs starting with `0.10.
|
603
|
+
**Note**: `task_id` field is available in logs starting with `0.10.0`
|
512
604
|
|
513
605
|
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.
|
514
606
|
|
@@ -571,26 +663,24 @@ By default jobs are retried 25 times - using an exponential backoff - before bei
|
|
571
663
|
|
572
664
|
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.
|
573
665
|
|
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.
|
666
|
+
**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.
|
575
667
|
|
576
668
|
E.g. Set max number of retries globally via the cloudtasker initializer.
|
577
669
|
```ruby
|
578
670
|
# config/initializers/cloudtasker.rb
|
579
671
|
|
580
672
|
Cloudtasker.configure do |config|
|
581
|
-
#
|
673
|
+
#
|
582
674
|
# Specify how many retries are allowed on jobs. This number of retries excludes any
|
583
675
|
# connectivity error that would be due to the application being down or unreachable.
|
584
|
-
#
|
676
|
+
#
|
585
677
|
# Default: 25
|
586
|
-
#
|
678
|
+
#
|
587
679
|
config.max_retries = 10
|
588
680
|
end
|
589
681
|
```
|
590
682
|
|
591
683
|
E.g. Set max number of retries to 3 on a given worker
|
592
|
-
|
593
|
-
E.g.
|
594
684
|
```ruby
|
595
685
|
# app/workers/some_error_worker.rb
|
596
686
|
|
@@ -600,7 +690,30 @@ class SomeErrorWorker
|
|
600
690
|
# This will override the global setting
|
601
691
|
cloudtasker_options max_retries: 3
|
602
692
|
|
603
|
-
def perform
|
693
|
+
def perform
|
694
|
+
raise(ArgumentError)
|
695
|
+
end
|
696
|
+
end
|
697
|
+
```
|
698
|
+
|
699
|
+
E.g. Evaluate the number of max retries at runtime (Supported since: v0.10.1)
|
700
|
+
```ruby
|
701
|
+
# app/workers/some_error_worker.rb
|
702
|
+
|
703
|
+
class SomeErrorWorker
|
704
|
+
include Cloudtasker::Worker
|
705
|
+
|
706
|
+
# Return the number of max retries based on
|
707
|
+
# worker arguments.
|
708
|
+
#
|
709
|
+
# If this method returns nil then max_retries
|
710
|
+
# will delegate to the class `max_retries` setting or Cloudtasker
|
711
|
+
# `max_retries` configuration otion.
|
712
|
+
def max_retries(arg1, arg2)
|
713
|
+
arg1 == 'foo' ? 13 : nil
|
714
|
+
end
|
715
|
+
|
716
|
+
def perform(arg1, arg2)
|
604
717
|
raise(ArgumentError)
|
605
718
|
end
|
606
719
|
end
|
@@ -618,7 +731,7 @@ require 'cloudtasker/testing'
|
|
618
731
|
# Mode 1 (default): Push jobs to Google Cloud Tasks (env != development) or Redis (env == development)
|
619
732
|
Cloudtasker::Testing.enable!
|
620
733
|
|
621
|
-
# Mode 2: Push jobs to an in-memory queue. Jobs will not be processed until you call
|
734
|
+
# Mode 2: Push jobs to an in-memory queue. Jobs will not be processed until you call
|
622
735
|
# Cloudtasker::Worker.drain_all (process all jobs) or MyWorker.drain (process jobs for specific worker)
|
623
736
|
Cloudtasker::Testing.fake!
|
624
737
|
|
@@ -682,9 +795,9 @@ Below are examples of rspec tests. It is assumed that `Cloudtasker::Testing.fake
|
|
682
795
|
**Example 1**: Testing that a job is scheduled
|
683
796
|
```ruby
|
684
797
|
describe 'worker scheduling'
|
685
|
-
subject(:enqueue_job) { MyWorker.perform_async(1,2) }
|
686
|
-
|
687
|
-
it { expect { enqueue_job }.to change(MyWorker.jobs, :size).by(1) }
|
798
|
+
subject(:enqueue_job) { MyWorker.perform_async(1,2) }
|
799
|
+
|
800
|
+
it { expect { enqueue_job }.to change(MyWorker.jobs, :size).by(1) }
|
688
801
|
end
|
689
802
|
```
|
690
803
|
|
@@ -692,7 +805,7 @@ end
|
|
692
805
|
```ruby
|
693
806
|
describe 'worker calls api'
|
694
807
|
subject { Cloudtasker::Testing.inline! { MyApiWorker.perform_async(1,2) } }
|
695
|
-
|
808
|
+
|
696
809
|
before { expect(MyApi).to receive(:fetch).and_return([]) }
|
697
810
|
it { is_expected.to be_truthy }
|
698
811
|
end
|
@@ -772,7 +885,7 @@ Google Cloud Tasks enforces a limit of 100 KB for job payloads. Taking into acco
|
|
772
885
|
Any excessive job payload (> 100 KB) will raise a `Cloudtasker::MaxTaskSizeExceededError`, both in production and development mode.
|
773
886
|
|
774
887
|
#### Option 1: Use Cloudtasker optional support for payload storage in Redis
|
775
|
-
**Supported since**: `0.10.
|
888
|
+
**Supported since**: `0.10.0`
|
776
889
|
|
777
890
|
Cloudtasker provides optional support for storing argument payloads in Redis instead of sending them to Google Cloud Tasks.
|
778
891
|
|
@@ -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'
|
@@ -41,8 +41,10 @@ Gem::Specification.new do |spec|
|
|
41
41
|
spec.add_development_dependency 'github_changelog_generator'
|
42
42
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
43
43
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
|
+
spec.add_development_dependency 'rspec-json_expectations', '~> 2.2'
|
44
45
|
spec.add_development_dependency 'rubocop', '0.76.0'
|
45
46
|
spec.add_development_dependency 'rubocop-rspec', '1.37.0'
|
47
|
+
spec.add_development_dependency 'semantic_logger'
|
46
48
|
spec.add_development_dependency 'timecop'
|
47
49
|
spec.add_development_dependency 'webmock'
|
48
50
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ActiveJob docs: http://guides.rubyonrails.org/active_job_basics.html
|
4
|
+
# Example adapters ref: https://github.com/rails/rails/tree/master/activejob/lib/active_job/queue_adapters
|
5
|
+
|
6
|
+
module ActiveJob
|
7
|
+
module QueueAdapters
|
8
|
+
# == Cloudtasker adapter for Active Job
|
9
|
+
#
|
10
|
+
# To use Cloudtasker set the queue_adapter config to +:cloudtasker+.
|
11
|
+
#
|
12
|
+
# Rails.application.config.active_job.queue_adapter = :cloudtasker
|
13
|
+
class CloudtaskerAdapter
|
14
|
+
SERIALIZATION_FILTERED_KEYS = [
|
15
|
+
'executions', # Given by the worker at processing
|
16
|
+
'provider_job_id', # Also given by the worker at processing
|
17
|
+
'priority' # Not used
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
# Enqueues the given ActiveJob instance for execution
|
21
|
+
#
|
22
|
+
# @param job [ActiveJob::Base] The ActiveJob instance
|
23
|
+
#
|
24
|
+
# @return [Cloudtasker::CloudTask] The Google Task response
|
25
|
+
#
|
26
|
+
def enqueue(job)
|
27
|
+
build_worker(job).schedule
|
28
|
+
end
|
29
|
+
|
30
|
+
# Enqueues the given ActiveJob instance for execution at a given time
|
31
|
+
#
|
32
|
+
# @param job [ActiveJob::Base] The ActiveJob instance
|
33
|
+
# @param precise_timestamp [Integer] The timestamp at which the job must be executed
|
34
|
+
#
|
35
|
+
# @return [Cloudtasker::CloudTask] The Google Task response
|
36
|
+
#
|
37
|
+
def enqueue_at(job, precise_timestamp)
|
38
|
+
build_worker(job).schedule(time_at: Time.at(precise_timestamp))
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def build_worker(job)
|
44
|
+
job_serialization = job.serialize.except(*SERIALIZATION_FILTERED_KEYS)
|
45
|
+
|
46
|
+
JobWrapper.new(
|
47
|
+
job_id: job_serialization.delete('job_id'),
|
48
|
+
job_queue: job_serialization.delete('queue_name'),
|
49
|
+
job_args: [job_serialization]
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# == Job Wrapper for the Cloudtasker adapter
|
54
|
+
#
|
55
|
+
# Executes jobs scheduled by the Cloudtasker ActiveJob adapter
|
56
|
+
class JobWrapper #:nodoc:
|
57
|
+
include Cloudtasker::Worker
|
58
|
+
|
59
|
+
# Executes the given serialized ActiveJob call.
|
60
|
+
# - See https://api.rubyonrails.org/classes/ActiveJob/Core.html#method-i-serialize
|
61
|
+
#
|
62
|
+
# @param [Hash] job_serialization The serialized ActiveJob call
|
63
|
+
#
|
64
|
+
# @return [any] The execution of the ActiveJob call
|
65
|
+
#
|
66
|
+
def perform(job_serialization, *_extra_options)
|
67
|
+
job_executions = job_retries < 1 ? 0 : (job_retries + 1)
|
68
|
+
|
69
|
+
job_serialization.merge!(
|
70
|
+
'job_id' => job_id,
|
71
|
+
'queue_name' => job_queue,
|
72
|
+
'provider_job_id' => task_id,
|
73
|
+
'executions' => job_executions,
|
74
|
+
'priority' => nil
|
75
|
+
)
|
76
|
+
|
77
|
+
Base.execute job_serialization
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
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'
|
@@ -166,6 +166,9 @@ module Cloudtasker
|
|
166
166
|
# Delete task
|
167
167
|
self.class.delete(id)
|
168
168
|
resp
|
169
|
+
rescue DeadWorkerError => e
|
170
|
+
self.class.delete(id)
|
171
|
+
raise(e) if self.class.inline_mode?
|
169
172
|
rescue StandardError => e
|
170
173
|
self.job_retries += 1
|
171
174
|
raise(e) if self.class.inline_mode?
|
data/lib/cloudtasker/cli.rb
CHANGED
data/lib/cloudtasker/engine.rb
CHANGED
@@ -5,12 +5,18 @@ module Cloudtasker
|
|
5
5
|
class Engine < ::Rails::Engine
|
6
6
|
isolate_namespace Cloudtasker
|
7
7
|
|
8
|
+
# Setup cloudtasker processing route
|
8
9
|
initializer 'cloudtasker', before: :load_config_initializers do
|
9
10
|
Rails.application.routes.append do
|
10
11
|
mount Cloudtasker::Engine, at: '/cloudtasker'
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
15
|
+
# Setup active job adapter
|
16
|
+
initializer 'cloudtasker.active_job', after: :load_config_initializers do
|
17
|
+
require 'active_job/queue_adapters/cloudtasker_adapter' if defined?(::ActiveJob::Railtie)
|
18
|
+
end
|
19
|
+
|
14
20
|
config.generators do |g|
|
15
21
|
g.test_framework :rspec, fixture: false
|
16
22
|
g.assets false
|
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.rc2
|
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-11-23 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
|
@@ -178,6 +178,20 @@ dependencies:
|
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '3.0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: rspec-json_expectations
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '2.2'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '2.2'
|
181
195
|
- !ruby/object:Gem::Dependency
|
182
196
|
name: rubocop
|
183
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +220,20 @@ dependencies:
|
|
206
220
|
- - '='
|
207
221
|
- !ruby/object:Gem::Version
|
208
222
|
version: 1.37.0
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: semantic_logger
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
209
237
|
- !ruby/object:Gem::Dependency
|
210
238
|
name: timecop
|
211
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -324,6 +352,12 @@ files:
|
|
324
352
|
- gemfiles/rails_5.2.gemfile.lock
|
325
353
|
- gemfiles/rails_6.0.gemfile
|
326
354
|
- gemfiles/rails_6.0.gemfile.lock
|
355
|
+
- gemfiles/semantic_logger_3.4.gemfile
|
356
|
+
- gemfiles/semantic_logger_4.6.gemfile
|
357
|
+
- gemfiles/semantic_logger_4.7.0.gemfile
|
358
|
+
- gemfiles/semantic_logger_4.7.2.gemfile
|
359
|
+
- gemfiles/semantic_logger_4.7.gemfile
|
360
|
+
- lib/active_job/queue_adapters/cloudtasker_adapter.rb
|
327
361
|
- lib/cloudtasker.rb
|
328
362
|
- lib/cloudtasker/authentication_error.rb
|
329
363
|
- lib/cloudtasker/authenticator.rb
|
@@ -351,6 +385,7 @@ files:
|
|
351
385
|
- lib/cloudtasker/max_task_size_exceeded_error.rb
|
352
386
|
- lib/cloudtasker/meta_store.rb
|
353
387
|
- lib/cloudtasker/middleware/chain.rb
|
388
|
+
- lib/cloudtasker/missing_worker_arguments_error.rb
|
354
389
|
- lib/cloudtasker/redis_client.rb
|
355
390
|
- lib/cloudtasker/testing.rb
|
356
391
|
- lib/cloudtasker/unique_job.rb
|