cloudtasker 0.11.1 → 0.12.rc1

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.
@@ -68,36 +68,16 @@ GEM
68
68
  minitest (~> 5.1)
69
69
  tzinfo (~> 1.1)
70
70
  zeitwerk (~> 2.1, >= 2.1.8)
71
- addressable (2.8.0)
71
+ addressable (2.7.0)
72
72
  public_suffix (>= 2.0.2, < 5.0)
73
- appraisal (2.4.1)
73
+ appraisal (2.4.0)
74
74
  bundler
75
75
  rake
76
76
  thor (>= 0.14.0)
77
77
  ast (2.4.2)
78
- async (1.30.1)
79
- console (~> 1.10)
80
- nio4r (~> 2.3)
81
- timers (~> 4.1)
82
- async-http (0.56.5)
83
- async (>= 1.25)
84
- async-io (>= 1.28)
85
- async-pool (>= 0.2)
86
- protocol-http (~> 0.22.0)
87
- protocol-http1 (~> 0.14.0)
88
- protocol-http2 (~> 0.14.0)
89
- async-http-faraday (0.11.0)
90
- async-http (~> 0.42)
91
- faraday
92
- async-io (1.32.2)
93
- async
94
- async-pool (0.3.8)
95
- async (>= 1.25)
96
78
  builder (3.2.4)
97
- concurrent-ruby (1.1.9)
98
- connection_pool (2.2.5)
99
- console (1.13.1)
100
- fiber-local
79
+ concurrent-ruby (1.1.8)
80
+ connection_pool (2.2.3)
101
81
  crack (0.4.5)
102
82
  rexml
103
83
  crass (1.0.6)
@@ -105,42 +85,26 @@ GEM
105
85
  erubi (1.10.0)
106
86
  et-orbi (1.2.4)
107
87
  tzinfo
108
- faraday (1.7.0)
109
- faraday-em_http (~> 1.0)
110
- faraday-em_synchrony (~> 1.0)
111
- faraday-excon (~> 1.1)
112
- faraday-httpclient (~> 1.0.1)
88
+ faraday (1.3.0)
113
89
  faraday-net_http (~> 1.0)
114
- faraday-net_http_persistent (~> 1.1)
115
- faraday-patron (~> 1.0)
116
- faraday-rack (~> 1.0)
117
90
  multipart-post (>= 1.2, < 3)
118
- ruby2_keywords (>= 0.0.4)
119
- faraday-em_http (1.0.0)
120
- faraday-em_synchrony (1.0.0)
121
- faraday-excon (1.1.0)
91
+ ruby2_keywords
122
92
  faraday-http-cache (2.2.0)
123
93
  faraday (>= 0.8)
124
- faraday-httpclient (1.0.1)
125
94
  faraday-net_http (1.0.1)
126
- faraday-net_http_persistent (1.2.0)
127
- faraday-patron (1.0.0)
128
- faraday-rack (1.0.0)
129
- fiber-local (1.0.0)
130
- fugit (1.5.1)
95
+ fugit (1.4.2)
131
96
  et-orbi (~> 1.1, >= 1.1.8)
132
97
  raabro (~> 1.4)
133
- github_changelog_generator (1.16.4)
98
+ github_changelog_generator (1.15.2)
134
99
  activesupport
135
- async (>= 1.25.0)
136
- async-http-faraday
137
100
  faraday-http-cache
138
101
  multi_json
139
102
  octokit (~> 4.6)
140
103
  rainbow (>= 2.2.1)
141
104
  rake (>= 10.0)
142
- globalid (0.5.2)
143
- activesupport (>= 5.0)
105
+ retriable (~> 3.0)
106
+ globalid (0.4.2)
107
+ activesupport (>= 4.2.0)
144
108
  google-cloud-tasks (1.5.1)
145
109
  google-gax (~> 1.8)
146
110
  googleapis-common-protos (>= 1.3.9, < 2.0)
@@ -152,33 +116,33 @@ GEM
152
116
  googleauth (~> 0.9)
153
117
  grpc (~> 1.24)
154
118
  rly (~> 0.2.3)
155
- google-protobuf (3.17.3)
119
+ google-protobuf (3.15.5)
156
120
  googleapis-common-protos (1.3.11)
157
121
  google-protobuf (~> 3.14)
158
122
  googleapis-common-protos-types (>= 1.0.6, < 2.0)
159
123
  grpc (~> 1.27)
160
- googleapis-common-protos-types (1.1.0)
124
+ googleapis-common-protos-types (1.0.6)
161
125
  google-protobuf (~> 3.14)
162
- googleauth (0.17.0)
126
+ googleauth (0.16.0)
163
127
  faraday (>= 0.17.3, < 2.0)
164
128
  jwt (>= 1.4, < 3.0)
165
129
  memoist (~> 0.16)
166
130
  multi_json (~> 1.11)
167
131
  os (>= 0.9, < 2.0)
168
132
  signet (~> 0.14)
169
- grpc (1.38.0)
170
- google-protobuf (~> 3.15)
133
+ grpc (1.36.0)
134
+ google-protobuf (~> 3.14)
171
135
  googleapis-common-protos-types (~> 1.0)
172
136
  grpc-google-iam-v1 (0.6.11)
173
137
  google-protobuf (~> 3.14)
174
138
  googleapis-common-protos (>= 1.3.11, < 2.0)
175
139
  grpc (~> 1.27)
176
140
  hashdiff (1.0.1)
177
- i18n (1.8.10)
141
+ i18n (1.8.9)
178
142
  concurrent-ruby (~> 1.0)
179
143
  jaro_winkler (1.5.4)
180
- jwt (2.2.3)
181
- loofah (2.12.0)
144
+ jwt (2.2.2)
145
+ loofah (2.9.0)
182
146
  crass (~> 1.0.2)
183
147
  nokogiri (>= 1.5.9)
184
148
  mail (2.7.1)
@@ -187,32 +151,23 @@ GEM
187
151
  mimemagic (~> 0.3.2)
188
152
  memoist (0.16.2)
189
153
  method_source (1.0.0)
190
- mimemagic (0.3.10)
191
- nokogiri (~> 1)
192
- rake
193
- mini_mime (1.1.1)
194
- mini_portile2 (2.6.1)
154
+ mimemagic (0.3.5)
155
+ mini_mime (1.0.2)
156
+ mini_portile2 (2.5.0)
195
157
  minitest (5.14.4)
196
158
  multi_json (1.15.0)
197
159
  multipart-post (2.1.1)
198
- nio4r (2.5.8)
199
- nokogiri (1.12.3)
200
- mini_portile2 (~> 2.6.1)
160
+ nio4r (2.5.7)
161
+ nokogiri (1.11.2)
162
+ mini_portile2 (~> 2.5.0)
201
163
  racc (~> 1.4)
202
- octokit (4.21.0)
164
+ octokit (4.20.0)
203
165
  faraday (>= 0.9)
204
166
  sawyer (~> 0.8.0, >= 0.5.3)
205
167
  os (1.1.1)
206
168
  parallel (1.20.1)
207
- parser (3.0.2.0)
169
+ parser (3.0.0.0)
208
170
  ast (~> 2.4.1)
209
- protocol-hpack (1.4.2)
210
- protocol-http (0.22.5)
211
- protocol-http1 (0.14.2)
212
- protocol-http (~> 0.22)
213
- protocol-http2 (0.14.2)
214
- protocol-hpack (~> 1.4)
215
- protocol-http (~> 0.18)
216
171
  public_suffix (4.0.6)
217
172
  raabro (1.4.0)
218
173
  racc (1.5.2)
@@ -237,7 +192,7 @@ GEM
237
192
  rails-dom-testing (2.0.3)
238
193
  activesupport (>= 4.2.0)
239
194
  nokogiri (>= 1.6)
240
- rails-html-sanitizer (1.4.2)
195
+ rails-html-sanitizer (1.3.0)
241
196
  loofah (~> 2.3)
242
197
  railties (6.0.0)
243
198
  actionpack (= 6.0.0)
@@ -246,10 +201,10 @@ GEM
246
201
  rake (>= 0.8.7)
247
202
  thor (>= 0.20.3, < 2.0)
248
203
  rainbow (3.0.0)
249
- rake (13.0.6)
250
- redis (4.4.0)
204
+ rake (13.0.3)
205
+ redis (4.2.5)
251
206
  retriable (3.1.2)
252
- rexml (3.2.5)
207
+ rexml (3.2.4)
253
208
  rly (0.2.3)
254
209
  rspec (3.10.0)
255
210
  rspec-core (~> 3.10.0)
@@ -264,7 +219,7 @@ GEM
264
219
  rspec-mocks (3.10.2)
265
220
  diff-lcs (>= 1.2.0, < 2.0)
266
221
  rspec-support (~> 3.10.0)
267
- rspec-rails (5.0.2)
222
+ rspec-rails (5.0.0)
268
223
  actionpack (>= 5.2)
269
224
  activesupport (>= 5.2)
270
225
  railties (>= 5.2)
@@ -283,11 +238,11 @@ GEM
283
238
  rubocop-rspec (1.37.0)
284
239
  rubocop (>= 0.68.1)
285
240
  ruby-progressbar (1.11.0)
286
- ruby2_keywords (0.0.5)
241
+ ruby2_keywords (0.0.4)
287
242
  sawyer (0.8.2)
288
243
  addressable (>= 2.3.5)
289
244
  faraday (> 0.8, < 2.0)
290
- semantic_logger (4.8.2)
245
+ semantic_logger (4.7.4)
291
246
  concurrent-ruby (~> 1.0)
292
247
  signet (0.15.0)
293
248
  addressable (~> 2.3)
@@ -305,15 +260,14 @@ GEM
305
260
  thor (1.1.0)
306
261
  thread_safe (0.3.6)
307
262
  timecop (0.9.4)
308
- timers (4.3.3)
309
263
  tzinfo (1.2.9)
310
264
  thread_safe (~> 0.1)
311
265
  unicode-display_width (1.6.1)
312
- webmock (3.14.0)
313
- addressable (>= 2.8.0)
266
+ webmock (3.12.1)
267
+ addressable (>= 2.3.6)
314
268
  crack (>= 0.3.2)
315
269
  hashdiff (>= 0.4.0, < 2.0.0)
316
- websocket-driver (0.7.5)
270
+ websocket-driver (0.7.3)
317
271
  websocket-extensions (>= 0.1.0)
318
272
  websocket-extensions (0.1.5)
319
273
  zeitwerk (2.4.2)
@@ -23,14 +23,12 @@ module Cloudtasker
23
23
  #
24
24
  # Return a namespaced key.
25
25
  #
26
- # @param [String, Symbol] val The key to namespace
26
+ # @param [String, Symbol, nil] val The key to namespace
27
27
  #
28
28
  # @return [String] The namespaced key.
29
29
  #
30
- def self.key(val)
31
- return nil if val.nil?
32
-
33
- [to_s.underscore, val.to_s].join('/')
30
+ def self.key(val = nil)
31
+ [to_s.underscore, val].compact.map(&:to_s).join('/')
34
32
  end
35
33
 
36
34
  #
@@ -39,9 +37,17 @@ module Cloudtasker
39
37
  # @return [Array<Cloudtasker::Backend::RedisTask>] All the tasks.
40
38
  #
41
39
  def self.all
42
- redis.search(key('*')).map do |gid|
43
- payload = redis.fetch(gid)
44
- new(payload.merge(id: gid.sub(key(''), '')))
40
+ if redis.exists?(key)
41
+ # Use Schedule Set if available
42
+ redis.smembers(key).map { |id| find(id) }
43
+ else
44
+ # Fallback to redis key matching and migrate tasks
45
+ # to use Task Set instead.
46
+ redis.search(key('*')).map do |gid|
47
+ task_id = gid.sub(key(''), '')
48
+ redis.sadd(key, task_id)
49
+ find(task_id)
50
+ end
45
51
  end
46
52
  end
47
53
 
@@ -82,6 +88,7 @@ module Cloudtasker
82
88
 
83
89
  # Save job
84
90
  redis.write(key(id), payload)
91
+ redis.sadd(key, id)
85
92
  new(payload.merge(id: id))
86
93
  end
87
94
 
@@ -105,6 +112,7 @@ module Cloudtasker
105
112
  # @param [String] id The task id.
106
113
  #
107
114
  def self.delete(id)
115
+ redis.srem(key, id)
108
116
  redis.del(key(id))
109
117
  end
110
118
 
@@ -176,7 +184,7 @@ module Cloudtasker
176
184
  # Remove the task from the queue.
177
185
  #
178
186
  def destroy
179
- redis.del(gid)
187
+ self.class.delete(id)
180
188
  end
181
189
 
182
190
  #
@@ -13,6 +13,10 @@ module Cloudtasker
13
13
  # List of statuses triggering a completion callback
14
14
  COMPLETION_STATUSES = %w[completed dead].freeze
15
15
 
16
+ # These callbacks do not need to raise errors on their own
17
+ # because the jobs will be either retried or dropped
18
+ IGNORED_ERRORED_CALLBACKS = %i[on_child_error on_child_dead].freeze
19
+
16
20
  #
17
21
  # Return the cloudtasker redis client
18
22
  #
@@ -250,8 +254,8 @@ module Cloudtasker
250
254
  end
251
255
 
252
256
  #
253
- # Run worker callback in a controlled environment to
254
- # avoid interruption of the callback flow.
257
+ # Run worker callback. The error and dead callbacks get
258
+ # silenced should they raise an error.
255
259
  #
256
260
  # @param [String, Symbol] callback The callback to run.
257
261
  # @param [Array<any>] *args The callback arguments.
@@ -261,9 +265,15 @@ module Cloudtasker
261
265
  def run_worker_callback(callback, *args)
262
266
  worker.try(callback, *args)
263
267
  rescue StandardError => e
264
- Cloudtasker.logger.error("Error running callback #{callback}: #{e}")
265
- Cloudtasker.logger.error(e.backtrace.join("\n"))
266
- nil
268
+ # There is no point in retrying jobs due to failure callbacks failing
269
+ # Only completion callbacks will trigger a re-run of the job because
270
+ # these do matter for batch completion
271
+ raise(e) unless IGNORED_ERRORED_CALLBACKS.include?(callback)
272
+
273
+ # Log error instead
274
+ worker.logger.error(
275
+ ["Callback #{callback} failed to run. Skipping to preserve error flow.", e, e.backtrace].flatten.join("\n")
276
+ )
267
277
  end
268
278
 
269
279
  #
@@ -275,7 +285,7 @@ module Cloudtasker
275
285
 
276
286
  # Propagate event
277
287
  parent_batch&.on_child_complete(self, status)
278
- ensure
288
+
279
289
  # The batch tree is complete. Cleanup the tree.
280
290
  cleanup unless parent_batch
281
291
  end
@@ -21,14 +21,12 @@ module Cloudtasker
21
21
  #
22
22
  # Return a namespaced key.
23
23
  #
24
- # @param [String, Symbol] val The key to namespace
24
+ # @param [String, Symbol, nil] val The key to namespace
25
25
  #
26
26
  # @return [String] The namespaced key.
27
27
  #
28
- def self.key(val)
29
- return nil if val.nil?
30
-
31
- [to_s.underscore, val.to_s].join('/')
28
+ def self.key(val = nil)
29
+ [to_s.underscore, val].compact.map(&:to_s).join('/')
32
30
  end
33
31
 
34
32
  #
@@ -37,8 +35,17 @@ module Cloudtasker
37
35
  # @return [Array<Cloudtasker::Batch::Schedule>] The list of stored schedules.
38
36
  #
39
37
  def self.all
40
- redis.search(key('*')).map do |gid|
41
- find(gid.sub(key(''), ''))
38
+ if redis.exists?(key)
39
+ # Use Schedule Set if available
40
+ redis.smembers(key).map { |id| find(id) }
41
+ else
42
+ # Fallback to redis key matching and migrate schedules
43
+ # to use Schedule Set instead.
44
+ redis.search(key('*')).map do |gid|
45
+ schedule_id = gid.sub(key(''), '')
46
+ redis.sadd(key, schedule_id)
47
+ find(schedule_id)
48
+ end
42
49
  end
43
50
  end
44
51
 
@@ -90,7 +97,7 @@ module Cloudtasker
90
97
  end
91
98
 
92
99
  #
93
- # Destroy a schedule by id.
100
+ # Delete a schedule by id.
94
101
  #
95
102
  # @param [String] id The schedule id.
96
103
  #
@@ -101,6 +108,7 @@ module Cloudtasker
101
108
 
102
109
  # Delete task and stored schedule
103
110
  CloudTask.delete(schedule.task_id) if schedule.task_id
111
+ redis.srem(key, schedule.id)
104
112
  redis.del(schedule.gid)
105
113
  end
106
114
  end
@@ -270,6 +278,7 @@ module Cloudtasker
270
278
 
271
279
  # Save schedule
272
280
  config_was_changed = config_changed?
281
+ redis.sadd(self.class.key, id)
273
282
  redis.write(gid, to_h)
274
283
 
275
284
  # Stop there if backend does not need update
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cloudtasker
4
- VERSION = '0.11.1'
4
+ VERSION = '0.12.rc1'
5
5
  end
@@ -311,13 +311,25 @@ module Cloudtasker
311
311
  end
312
312
 
313
313
  #
314
- # Return true if the job has excceeded its maximum number
315
- # of retries
314
+ # Return true if the job must declared dead upon raising
315
+ # an error.
316
+ #
317
+ # @return [Boolean] True if the job must die on error.
318
+ #
319
+ def job_must_die?
320
+ job_retries >= job_max_retries
321
+ end
322
+
323
+ #
324
+ # Return true if the job has strictly excceeded its maximum number
325
+ # of retries.
326
+ #
327
+ # Used a preemptive filter when running the job.
316
328
  #
317
329
  # @return [Boolean] True if the job is dead
318
330
  #
319
331
  def job_dead?
320
- job_retries >= job_max_retries
332
+ job_retries > job_max_retries
321
333
  end
322
334
 
323
335
  #
@@ -332,11 +344,35 @@ module Cloudtasker
332
344
  (perform_ended_at - perform_started_at).ceil(3)
333
345
  end
334
346
 
347
+ #
348
+ # Run worker callback.
349
+ #
350
+ # @param [String, Symbol] callback The callback to run.
351
+ # @param [Array<any>] *args The callback arguments.
352
+ #
353
+ # @return [any] The callback return value
354
+ #
355
+ def run_callback(callback, *args)
356
+ try(callback, *args)
357
+ end
358
+
335
359
  #=============================
336
360
  # Private
337
361
  #=============================
338
362
  private
339
363
 
364
+ #
365
+ # Flag the worker as dead by invoking the on_dead hook
366
+ # and raising a DeadWorkerError
367
+ #
368
+ # @param [Exception, nil] error An optional exception to be passed to the DeadWorkerError.
369
+ #
370
+ def flag_as_dead(error = nil)
371
+ run_callback(:on_dead, error || DeadWorkerError.new)
372
+ ensure
373
+ raise(DeadWorkerError, error)
374
+ end
375
+
340
376
  #
341
377
  # Execute the worker perform method through the middleware chain.
342
378
  #
@@ -346,6 +382,9 @@ module Cloudtasker
346
382
  self.perform_started_at = Time.now
347
383
 
348
384
  Cloudtasker.config.server_middleware.invoke(self) do
385
+ # Immediately abort the job if it is already dead
386
+ flag_as_dead if job_dead?
387
+
349
388
  begin
350
389
  # Abort if arguments are missing. This may happen with redis arguments storage
351
390
  # if Cloud Tasks times out on a job but the job still succeeds
@@ -356,12 +395,11 @@ module Cloudtasker
356
395
  # Perform the job
357
396
  perform(*job_args)
358
397
  rescue StandardError => e
359
- try(:on_error, e)
360
- return raise(e) unless job_dead?
398
+ run_callback(:on_error, e)
399
+ return raise(e) unless job_must_die?
361
400
 
362
401
  # Flag job as dead
363
- try(:on_dead, e)
364
- raise(DeadWorkerError, e)
402
+ flag_as_dead(e)
365
403
  end
366
404
  end
367
405
  ensure
@@ -45,6 +45,28 @@ module Cloudtasker
45
45
  end
46
46
  end
47
47
 
48
+ #
49
+ # Log error on execution failure.
50
+ #
51
+ # @param [Cloudtasker::Worker, nil] worker The worker.
52
+ # @param [Exception] error The error to log.
53
+ #
54
+ # @void
55
+ #
56
+ def self.log_execution_error(worker, error)
57
+ # ActiveJob has its own error logging. No need to double log the error.
58
+ # Note: we use string matching instead of class matching as
59
+ # ActiveJob::QueueAdapters::CloudtaskerAdapter::JobWrapper might not be loaded
60
+ return if worker.class.to_s =~ /^ActiveJob::/
61
+
62
+ # Choose logger to use based on context
63
+ # Worker will be nil on InvalidWorkerError - in that case we use generic logging
64
+ logger = worker&.logger || Cloudtasker.logger
65
+
66
+ # Log error
67
+ logger.error([error, error.backtrace].flatten.join("\n"))
68
+ end
69
+
48
70
  #
49
71
  # Execute a task worker from a task payload
50
72
  #
@@ -88,6 +110,10 @@ module Cloudtasker
88
110
  rescue DeadWorkerError, MissingWorkerArgumentsError => e
89
111
  # Delete stored args payload if job is dead
90
112
  redis.expire(args_payload_key, ARGS_PAYLOAD_CLEANUP_TTL) if args_payload_key
113
+ log_execution_error(worker, e)
114
+ raise(e)
115
+ rescue StandardError => e
116
+ log_execution_error(worker, e)
91
117
  raise(e)
92
118
  end
93
119
 
data/lib/cloudtasker.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/string/inflections'
4
- require 'active_support/core_ext/object/try'
5
4
 
6
5
  require 'cloudtasker/version'
7
6
  require 'cloudtasker/config'
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.11.1
4
+ version: 0.12.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arnaud Lachaume
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-25 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
@@ -324,6 +324,7 @@ files:
324
324
  - README.md
325
325
  - Rakefile
326
326
  - _config.yml
327
+ - app/controllers/cloudtasker/application_controller.rb
327
328
  - app/controllers/cloudtasker/worker_controller.rb
328
329
  - bin/console
329
330
  - bin/setup
@@ -415,7 +416,7 @@ metadata:
415
416
  homepage_uri: https://github.com/keypup-io/cloudtasker
416
417
  source_code_uri: https://github.com/keypup-io/cloudtasker
417
418
  changelog_uri: https://github.com/keypup-io/cloudtasker/master/tree/CHANGELOG.md
418
- post_install_message:
419
+ post_install_message:
419
420
  rdoc_options: []
420
421
  require_paths:
421
422
  - lib
@@ -426,12 +427,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
426
427
  version: '0'
427
428
  required_rubygems_version: !ruby/object:Gem::Requirement
428
429
  requirements:
429
- - - ">="
430
+ - - ">"
430
431
  - !ruby/object:Gem::Version
431
- version: '0'
432
+ version: 1.3.1
432
433
  requirements: []
433
434
  rubygems_version: 3.0.0
434
- signing_key:
435
+ signing_key:
435
436
  specification_version: 4
436
437
  summary: Background jobs for Ruby using Google Cloud Tasks (beta)
437
438
  test_files: []