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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c5440326c0fa695e469a341d58b976bd4027179440a607ce34c15c9dd4d697e
4
- data.tar.gz: 671c2bcf90c199c68136a14efa62410d17e3a850dad1e6b5bf6a02b083169d75
3
+ metadata.gz: c5d91132bd3363bf3d7c08cd207b385f502eae3056be4734f73f60ef220f94c5
4
+ data.tar.gz: fe935947d1ece340202d32eb54927336840ca236510a5044c2b1996c2161080d
5
5
  SHA512:
6
- metadata.gz: f6adad797eedb6ff0d589ea2dfa6b109709f629ad82cf64e9755e7da235b4a4fb902d4c48794981dcd527a6933196686a72fce46b2572912fd84b1a3396bcd33
7
- data.tar.gz: 78fa73bafda04c16b46bed2bd8930e5e5ef3badf046f2e665b41d156e9644c20468788a0ffa173fcb6585bdb24a1bdcff599be11f41a5420c1302747e209bc52
6
+ metadata.gz: ed918a89e19dca6068f6c4d7b2aa0e55f838829f19741b1509edcbf3e03117e102454791efd808c5521da30e862d8cfc1d5969444cfb6ee250589e5b7dd281de
7
+ data.tar.gz: d4fbb9afebcf84efb8bbe3f11b126aefc93a14c62d9d09b1a2f27186defb9c30ee0d28c13688b6bcc38411a3cbe8a736010fbea1a75e7dfa6334870e0522f2ff
@@ -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
@@ -20,6 +20,10 @@ Metrics/LineLength:
20
20
  Metrics/MethodLength:
21
21
  Max: 20
22
22
 
23
+ RSpec/DescribeClass:
24
+ Exclude:
25
+ - 'spec/integration/**/*_spec.rb'
26
+
23
27
  RSpec/ExpectInHook:
24
28
  Enabled: false
25
29
 
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,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.rc1
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.rc6`
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.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.
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.rc1`
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
- 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'
@@ -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,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: "../"
@@ -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
@@ -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?
@@ -64,6 +64,7 @@ module Cloudtasker
64
64
  return false unless File.exist?('./config/environment.rb')
65
65
 
66
66
  require 'rails'
67
+ require 'cloudtasker/engine'
67
68
  require File.expand_path('./config/environment.rb')
68
69
  end
69
70
 
@@ -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
@@ -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.rc7'
4
+ VERSION = '0.11.rc2'
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.rc7
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-05-12 00:00:00.000000000 Z
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