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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 796e69d0470947fe0af6ea530cfd6c5b69e95b6a1be42b307e84b27dd9246198
4
- data.tar.gz: 5c51dd25b3033546a7115d83d638123e80a9ee26fb74845b88a02247edb39461
3
+ metadata.gz: 1a0759638a4af47fcc26467b93039b3c6355908db5cafd0a20972b7e48649b81
4
+ data.tar.gz: a0f76c953bc0f64276f5b10f90300bcf7415dbfe305fac6697e4bd9fb83b53c0
5
5
  SHA512:
6
- metadata.gz: 134e6d344e75a9500850b135ea215c2b64d31cf366892acc0aab1b37c47e758dfa7e2bd00015ec6d8bb47556aba002bb578f2365ea34196d3343b24cee0f2156
7
- data.tar.gz: 73bd3cbd91fa1938b97df2cd7f5125a3f90205fb31b5d12efe5b64c4c0aa03e74f6f83cd8c9267b95b5553be93395a45db4f365bbb49b5623a97bf982bf9d2d7
6
+ metadata.gz: ef16621727a56793623e1c582bac1f6e4971b8ff1af3ee36a3d37b22d239f7bb3504a35b5f1ffc9f250343b2ed5ae888e75b8d583b49487f34dc69eb45cd9d13
7
+ data.tar.gz: aea0fa6eb2873f6b99df05c613d89363a3c6103fe814123d25a7508ede1fbf92cf653d10e7d57863d7ec6065d1c96e5c5f3ecf371a75ac925a588693849c320e
@@ -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
@@ -40,4 +40,7 @@ Style/Documentation:
40
40
  - 'spec/**/*'
41
41
 
42
42
  Metrics/ParameterLists:
43
- CountKeywordArgs: false
43
+ CountKeywordArgs: false
44
+
45
+ RSpec/MessageSpies:
46
+ Enabled: false
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
@@ -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 in development. This local server processes jobs in lieu of Cloud Tasks and allows you to work offline.
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. [Best practices building workers](#best-practices-building-workers)
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 as:
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 that would be due to the application being down or unreachable.
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.rc1
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 worker 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.
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.rc6`
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 failing will automatically return an HTTP error to Cloud Task and trigger a retry at a later time. The number of Cloud Task retries Cloud Task will depend on the configuration of your queue in Cloud Tasks.
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 `0.10.rc3` Cloudtasker uses the `X-CloudTasks-TaskRetryCount` header to detect the number of retries. This header includes `HTTP 503` errors which means that if your application is down at some point, jobs will fail and these failures will be counted toward the maximum number of retries. A [bug report](https://issuetracker.google.com/issues/154532072) has been raised with GCP to address this issue. Once fixed we will revert to using `X-CloudTasks-TaskExecutionCount` to avoid counting `HTTP 503` as job failures.
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.rc1`
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
- Cloudtasker.logger.error(e)
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.
@@ -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
 
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "semantic_logger", "3.4.1"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "semantic_logger", "4.6.1"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "semantic_logger", "4.7.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "semantic_logger", "4.7.2"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "semantic_logger", "4.7.2"
6
+
7
+ gemspec path: "../"
@@ -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 defined?(Cloudtasker::Testing) && Cloudtasker::Testing.inline?
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 StandardError
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
  #
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudtasker
4
+ class MissingWorkerArgumentsError < StandardError
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cloudtasker
4
- VERSION = '0.10.rc6'
4
+ VERSION = '0.11.rc1'
5
5
  end
@@ -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 >= Cloudtasker.config.max_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.10.rc6
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-05-08 00:00:00.000000000 Z
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