cloudtasker 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a7bb0597bbd5656c6c6c273b4a65097c0b9c44be4b5944a5c1fbdf2d03f9c7c
4
- data.tar.gz: c1e1d33203c8dfa5a090427c91f7b94e6046fbc0037fbb74285e61851cab3f93
3
+ metadata.gz: 8f4e53fe9c99a8c6a74e064d7b7c562cdb671682aaed9bb6112a77c7fd453124
4
+ data.tar.gz: 3b2e6aeca4fde352acd7b0db6fe469221514bf7fd753c07c3d9540d881bf1c2f
5
5
  SHA512:
6
- metadata.gz: a03d48359589520a040e8214ed40d778aece0e667de81bfb15b447c4f4f93f296955b08624c9fa1a5e8ca5e5cb6773389b490566f076f0e1f50ebbf0c46d376a
7
- data.tar.gz: 5f64a1e30faa954e2e046f787c216b9c02a992493561abe5dbbef9a7678833ef1644947b4e6f3743ebe43c79c62d90c48b46c91a6002088f88e94e7689b91a6d
6
+ metadata.gz: 4be2ce9466e7ffd2cc274d57d79eedad96ae56d699632f88bfdf6384413aa9bfa19dff8a124926bd2b41229ce53f3126b2bd04646cc9125136bc93fdffb0642a
7
+ data.tar.gz: f6a3f7fa152f5ebedbe25945e86538691ee658c5bc02b840fd3d1f337ba0fcd78db6a84c52bcd6ade01af51335ee81d457152bfa9767d3ac9541b5d31ef48126
@@ -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
data/.rubocop.yml CHANGED
@@ -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
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.11.0](https://github.com/keypup-io/cloudtasker/tree/v0.11.0) (2020-11-23)
4
+
5
+ [Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.10.0...v0.11.0)
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
+
3
20
  ## [v0.10.0](https://github.com/keypup-io/cloudtasker/tree/v0.10.0) (2020-09-02)
4
21
 
5
22
  [Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.3...v0.10.0)
@@ -18,6 +35,13 @@
18
35
  - Google API: improve error handling on job creation
19
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)
20
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
+
21
45
  ## [v0.9.3](https://github.com/keypup-io/cloudtasker/tree/v0.9.3) (2020-06-25)
22
46
 
23
47
  [Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.9.2...v0.9.3)
@@ -96,7 +120,3 @@ For Sinatra applications please update your Cloudtasker controller according to
96
120
  ## [v0.1.0](https://github.com/keypup-io/cloudtasker/tree/v0.1.0) (2019-11-17)
97
121
 
98
122
  [Full Changelog](https://github.com/keypup-io/cloudtasker/compare/c137feb1ceaaaa4e2fecac0d1f0b4c73151ae002...v0.1.0)
99
-
100
-
101
-
102
- \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/README.md CHANGED
@@ -12,10 +12,13 @@ Cloudtasker also provides optional modules for running [cron jobs](docs/CRON_JOB
12
12
 
13
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
+ **Maturity**: This gem is production-ready. We at Keypup have already processed millions of jobs using Cloudtasker and all related extensions (cron, batch and unique jobs). I'm waiting till the end of 2020 before releasing the official `v1.0.0` in case we've missed any edge-case bug.
16
+
15
17
  ## Summary
16
18
 
17
19
  1. [Installation](#installation)
18
20
  2. [Get started with Rails](#get-started-with-rails)
21
+ 2. [Get started with Rails & ActiveJob](#get-started-with-rails--activejob)
19
22
  3. [Configuring Cloudtasker](#configuring-cloudtasker)
20
23
  1. [Cloud Tasks authentication & permissions](#cloud-tasks-authentication--permissions)
21
24
  2. [Cloudtasker initializer](#cloudtasker-initializer)
@@ -75,7 +78,7 @@ Cloudtasker.configure do |config|
75
78
  # Adapt the server port to be the one used by your Rails web process
76
79
  #
77
80
  config.processor_host = 'http://localhost:3000'
78
-
81
+
79
82
  #
80
83
  # If you do not have any Rails secret_key_base defined, uncomment the following
81
84
  # This secret is used to authenticate jobs sent to the processing endpoint
@@ -132,6 +135,95 @@ That's it! Your job was picked up by the Cloudtasker local server and sent for p
132
135
 
133
136
  Now jump to the next section to configure your app to use Google Cloud Tasks as a backend.
134
137
 
138
+ ## Get started with Rails & ActiveJob
139
+ **Note**: ActiveJob is supported since `0.11.0`
140
+ **Note**: Cloudtasker extensions (cron, batch and unique jobs) are not available when using cloudtasker via ActiveJob.
141
+
142
+ Cloudtasker is pre-integrated with ActiveJob. Follow the steps below to get started.
143
+
144
+ Install redis on your machine (this is required by the Cloudtasker local processing server)
145
+ ```bash
146
+ # E.g. using brew
147
+ brew install redis
148
+ ```
149
+
150
+ Add the following initializer
151
+ ```ruby
152
+ # config/initializers/cloudtasker.rb
153
+
154
+ Cloudtasker.configure do |config|
155
+ #
156
+ # Adapt the server port to be the one used by your Rails web process
157
+ #
158
+ config.processor_host = 'http://localhost:3000'
159
+
160
+ #
161
+ # If you do not have any Rails secret_key_base defined, uncomment the following
162
+ # This secret is used to authenticate jobs sent to the processing endpoint
163
+ # of your application.
164
+ #
165
+ # config.secret = 'some-long-token'
166
+ end
167
+ ```
168
+
169
+ Configure ActiveJob to use Cloudtasker. You can also configure ActiveJob per environment via the config/environments/:env.rb files
170
+ ```ruby
171
+ # config/application.rb
172
+
173
+ require_relative 'boot'
174
+ require 'rails/all'
175
+
176
+ Bundler.require(*Rails.groups)
177
+
178
+ module Dummy
179
+ class Application < Rails::Application
180
+ # Initialize configuration defaults for originally generated Rails version.
181
+ config.load_defaults 6.0
182
+
183
+ # Settings in config/environments/* take precedence over those specified here.
184
+ # Application configuration can go into files in config/initializers
185
+ # -- all .rb files in that directory are automatically loaded after loading
186
+ # the framework and any gems in your application.
187
+
188
+ # Use cloudtasker as the ActiveJob backend:
189
+ config.active_job.queue_adapter = :cloudtasker
190
+ end
191
+ end
192
+
193
+ ```
194
+
195
+ Define your first job:
196
+ ```ruby
197
+ # app/jobs/example_job.rb
198
+
199
+ class ExampleJob < ApplicationJob
200
+ queue_as :default
201
+
202
+ def perform(some_arg)
203
+ logger.info("Job run with #{some_arg}. This is working!")
204
+ end
205
+ end
206
+ ```
207
+
208
+ Launch Rails and the local Cloudtasker processing server (or add `cloudtasker` to your foreman config as a `worker` process)
209
+ ```bash
210
+ # In one terminal
211
+ > rails s -p 3000
212
+
213
+ # In another terminal
214
+ > cloudtasker
215
+ ```
216
+
217
+ Open a Rails console and enqueue some jobs
218
+ ```ruby
219
+ # Process job as soon as possible
220
+ ExampleJob.perform_later('foo')
221
+
222
+ # Process job in 60 seconds
223
+ ExampleJob.set(wait: 60).perform_later('foo')
224
+ ```
225
+
226
+
135
227
  ## Configuring Cloudtasker
136
228
 
137
229
  ### Cloud Tasks authentication & permissions
@@ -158,14 +250,14 @@ The gem can be configured through an initializer. See below all the available co
158
250
  Cloudtasker.configure do |config|
159
251
  #
160
252
  # 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
253
+ # This secret is used to authenticate jobs sent to the processing endpoint
162
254
  # of your application.
163
255
  #
164
256
  # Default with Rails: Rails.application.credentials.secret_key_base
165
257
  #
166
258
  # config.secret = 'some-long-token'
167
259
 
168
- #
260
+ #
169
261
  # Specify the details of your Google Cloud Task location.
170
262
  #
171
263
  # This not required in development using the Cloudtasker local server.
@@ -189,21 +281,21 @@ Cloudtasker.configure do |config|
189
281
  #
190
282
  # Specific queues can be created in Cloud Tasks using the gcloud SDK or
191
283
  # via the `rake cloudtasker:setup_queue name=<queue_name>` task.
192
- #
284
+ #
193
285
  config.gcp_queue_prefix = 'my-app'
194
286
 
195
- #
287
+ #
196
288
  # Specify the publicly accessible host for your application
197
289
  #
198
290
  # > E.g. in development, using the cloudtasker local server
199
291
  # config.processor_host = 'http://localhost:3000'
200
- #
292
+ #
201
293
  # > E.g. in development, using `config.mode = :production` and ngrok
202
294
  # config.processor_host = 'https://111111.ngrok.io'
203
295
  #
204
296
  config.processor_host = 'https://app.mydomain.com'
205
297
 
206
- #
298
+ #
207
299
  # Specify the mode of operation:
208
300
  # - :development => jobs will be pushed to Redis and picked up by the Cloudtasker local server
209
301
  # - :production => jobs will be pushed to Google Cloud Tasks. Requires a publicly accessible domain.
@@ -212,20 +304,20 @@ Cloudtasker.configure do |config|
212
304
  #
213
305
  # config.mode = Rails.env.production? || Rails.env.my_other_env? ? :production : :development
214
306
 
215
- #
307
+ #
216
308
  # Specify the logger to use
217
- #
309
+ #
218
310
  # Default with Rails: Rails.logger
219
311
  # Default without Rails: Logger.new(STDOUT)
220
- #
312
+ #
221
313
  # config.logger = MyLogger.new(STDOUT)
222
314
 
223
- #
315
+ #
224
316
  # Specify how many retries are allowed on jobs. This number of retries excludes any
225
317
  # connectivity error due to the application being down or unreachable.
226
- #
318
+ #
227
319
  # Default: 25
228
- #
320
+ #
229
321
  # config.max_retries = 10
230
322
 
231
323
  #
@@ -262,7 +354,7 @@ Cloudtasker.configure do |config|
262
354
  end
263
355
  ```
264
356
 
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).
357
+ 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
358
 
267
359
  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
360
  ```bash
@@ -361,10 +453,12 @@ CriticalWorker.schedule(args: [1], queue: :important)
361
453
  ```
362
454
 
363
455
  ## Extensions
456
+ **Note**: Extensions are not available when using cloudtasker via ActiveJob.
457
+
364
458
  Cloudtasker comes with three optional features:
365
459
  - Cron Jobs [[docs](docs/CRON_JOBS.md)]: Run jobs at fixed intervals.
366
460
  - 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.
461
+ - Unique Jobs [[docs](docs/UNIQUE_JOBS.md)]: Ensure uniqueness of jobs based on job arguments.
368
462
 
369
463
  ## Working locally
370
464
 
@@ -381,9 +475,9 @@ You can configure your application to use the Cloudtasker local server using the
381
475
 
382
476
  Cloudtasker.configure do |config|
383
477
  # ... other options
384
-
478
+
385
479
  # 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
480
+ # This is the default mode unless CLOUDTASKER_ENV or RAILS_ENV or RACK_ENV is set
387
481
  # to a non-development environment
388
482
  config.mode = :development
389
483
  end
@@ -427,7 +521,7 @@ Cloudtasker.configure do |config|
427
521
 
428
522
  # Use your ngrok domain as the processor host
429
523
  config.processor_host = 'https://your-tunnel-id.ngrok.io'
430
-
524
+
431
525
  # Force Cloudtasker to use Google Cloud Tasks in development
432
526
  config.mode = :production
433
527
  end
@@ -578,19 +672,17 @@ E.g. Set max number of retries globally via the cloudtasker initializer.
578
672
  # config/initializers/cloudtasker.rb
579
673
 
580
674
  Cloudtasker.configure do |config|
581
- #
675
+ #
582
676
  # Specify how many retries are allowed on jobs. This number of retries excludes any
583
677
  # connectivity error that would be due to the application being down or unreachable.
584
- #
678
+ #
585
679
  # Default: 25
586
- #
680
+ #
587
681
  config.max_retries = 10
588
682
  end
589
683
  ```
590
684
 
591
685
  E.g. Set max number of retries to 3 on a given worker
592
-
593
- E.g.
594
686
  ```ruby
595
687
  # app/workers/some_error_worker.rb
596
688
 
@@ -600,7 +692,30 @@ class SomeErrorWorker
600
692
  # This will override the global setting
601
693
  cloudtasker_options max_retries: 3
602
694
 
603
- def perform()
695
+ def perform
696
+ raise(ArgumentError)
697
+ end
698
+ end
699
+ ```
700
+
701
+ E.g. Evaluate the number of max retries at runtime (Supported since: v0.10.1)
702
+ ```ruby
703
+ # app/workers/some_error_worker.rb
704
+
705
+ class SomeErrorWorker
706
+ include Cloudtasker::Worker
707
+
708
+ # Return the number of max retries based on
709
+ # worker arguments.
710
+ #
711
+ # If this method returns nil then max_retries
712
+ # will delegate to the class `max_retries` setting or Cloudtasker
713
+ # `max_retries` configuration otion.
714
+ def max_retries(arg1, arg2)
715
+ arg1 == 'foo' ? 13 : nil
716
+ end
717
+
718
+ def perform(arg1, arg2)
604
719
  raise(ArgumentError)
605
720
  end
606
721
  end
@@ -618,7 +733,7 @@ require 'cloudtasker/testing'
618
733
  # Mode 1 (default): Push jobs to Google Cloud Tasks (env != development) or Redis (env == development)
619
734
  Cloudtasker::Testing.enable!
620
735
 
621
- # Mode 2: Push jobs to an in-memory queue. Jobs will not be processed until you call
736
+ # Mode 2: Push jobs to an in-memory queue. Jobs will not be processed until you call
622
737
  # Cloudtasker::Worker.drain_all (process all jobs) or MyWorker.drain (process jobs for specific worker)
623
738
  Cloudtasker::Testing.fake!
624
739
 
@@ -682,9 +797,9 @@ Below are examples of rspec tests. It is assumed that `Cloudtasker::Testing.fake
682
797
  **Example 1**: Testing that a job is scheduled
683
798
  ```ruby
684
799
  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) }
800
+ subject(:enqueue_job) { MyWorker.perform_async(1,2) }
801
+
802
+ it { expect { enqueue_job }.to change(MyWorker.jobs, :size).by(1) }
688
803
  end
689
804
  ```
690
805
 
@@ -692,7 +807,7 @@ end
692
807
  ```ruby
693
808
  describe 'worker calls api'
694
809
  subject { Cloudtasker::Testing.inline! { MyApiWorker.perform_async(1,2) } }
695
-
810
+
696
811
  before { expect(MyApi).to receive(:fetch).and_return([]) }
697
812
  it { is_expected.to be_truthy }
698
813
  end
@@ -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.
data/cloudtasker.gemspec CHANGED
@@ -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
 
data/docs/BATCH_JOBS.md CHANGED
@@ -1,4 +1,4 @@
1
- # Cloudtasker Unique Jobs
1
+ # Cloudtasker Batch Jobs
2
2
 
3
3
  **Note**: this extension requires redis
4
4
 
@@ -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
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?
@@ -62,6 +62,10 @@ module Cloudtasker
62
62
  # @return [Cloudtasker::Batch::Job] The attached batch.
63
63
  #
64
64
  def self.for(worker)
65
+ # Load extension if not loaded already on the worker class
66
+ worker.class.include(Extension::Worker) unless worker.class <= Extension::Worker
67
+
68
+ # Add batch capability
65
69
  worker.batch = new(worker)
66
70
  end
67
71
 
@@ -16,6 +16,8 @@ module Cloudtasker
16
16
  Cloudtasker.configure do |config|
17
17
  config.server_middleware { |c| c.add(Middleware::Server) }
18
18
  end
19
+
20
+ # Inject worker extension on main module
19
21
  Cloudtasker::Worker.include(Extension::Worker)
20
22
  end
21
23
  end
@@ -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,10 +5,14 @@ module Cloudtasker
5
5
  class Engine < ::Rails::Engine
6
6
  isolate_namespace Cloudtasker
7
7
 
8
- initializer 'cloudtasker', before: :load_config_initializers do
8
+ config.before_initialize do
9
+ # Mount cloudtasker processing endpoint
9
10
  Rails.application.routes.append do
10
11
  mount Cloudtasker::Engine, at: '/cloudtasker'
11
12
  end
13
+
14
+ # Add ActiveJob adapter
15
+ require 'active_job/queue_adapters/cloudtasker_adapter' if defined?(::ActiveJob::Railtie)
12
16
  end
13
17
 
14
18
  config.generators do |g|
@@ -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.0'
4
+ VERSION = '0.11.0'
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.0
4
+ version: 0.11.0
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-09-02 00:00:00.000000000 Z
11
+ date: 2021-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -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