cloudtasker 0.12.rc8 → 0.12.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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- cloudtasker (0.12.rc8)
4
+ cloudtasker (0.12.0)
5
5
  activesupport
6
6
  connection_pool
7
7
  fugit
@@ -68,35 +68,35 @@ GEM
68
68
  minitest (~> 5.1)
69
69
  tzinfo (~> 1.1)
70
70
  zeitwerk (~> 2.1, >= 2.1.8)
71
- addressable (2.7.0)
71
+ addressable (2.8.0)
72
72
  public_suffix (>= 2.0.2, < 5.0)
73
- appraisal (2.4.0)
73
+ appraisal (2.4.1)
74
74
  bundler
75
75
  rake
76
76
  thor (>= 0.14.0)
77
77
  ast (2.4.2)
78
- async (1.28.9)
78
+ async (1.30.1)
79
79
  console (~> 1.10)
80
80
  nio4r (~> 2.3)
81
81
  timers (~> 4.1)
82
- async-http (0.54.1)
83
- async (~> 1.25)
84
- async-io (~> 1.28)
85
- async-pool (~> 0.2)
86
- protocol-http (~> 0.21.0)
87
- protocol-http1 (~> 0.13.0)
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
88
  protocol-http2 (~> 0.14.0)
89
- async-http-faraday (0.9.0)
89
+ async-http-faraday (0.11.0)
90
90
  async-http (~> 0.42)
91
91
  faraday
92
- async-io (1.30.2)
93
- async (~> 1.14)
94
- async-pool (0.3.5)
95
- async (~> 1.25)
92
+ async-io (1.32.2)
93
+ async
94
+ async-pool (0.3.8)
95
+ async (>= 1.25)
96
96
  builder (3.2.4)
97
- concurrent-ruby (1.1.8)
98
- connection_pool (2.2.3)
99
- console (1.10.2)
97
+ concurrent-ruby (1.1.9)
98
+ connection_pool (2.2.5)
99
+ console (1.13.1)
100
100
  fiber-local
101
101
  crack (0.4.5)
102
102
  rexml
@@ -105,18 +105,32 @@ GEM
105
105
  erubi (1.10.0)
106
106
  et-orbi (1.2.4)
107
107
  tzinfo
108
- faraday (1.3.0)
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)
109
113
  faraday-net_http (~> 1.0)
114
+ faraday-net_http_persistent (~> 1.1)
115
+ faraday-patron (~> 1.0)
116
+ faraday-rack (~> 1.0)
110
117
  multipart-post (>= 1.2, < 3)
111
- ruby2_keywords
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)
112
122
  faraday-http-cache (2.2.0)
113
123
  faraday (>= 0.8)
124
+ faraday-httpclient (1.0.1)
114
125
  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)
115
129
  fiber-local (1.0.0)
116
- fugit (1.4.4)
130
+ fugit (1.5.1)
117
131
  et-orbi (~> 1.1, >= 1.1.8)
118
132
  raabro (~> 1.4)
119
- github_changelog_generator (1.16.1)
133
+ github_changelog_generator (1.16.4)
120
134
  activesupport
121
135
  async (>= 1.25.0)
122
136
  async-http-faraday
@@ -125,9 +139,8 @@ GEM
125
139
  octokit (~> 4.6)
126
140
  rainbow (>= 2.2.1)
127
141
  rake (>= 10.0)
128
- retriable (~> 3.0)
129
- globalid (0.4.2)
130
- activesupport (>= 4.2.0)
142
+ globalid (0.5.2)
143
+ activesupport (>= 5.0)
131
144
  google-cloud-tasks (1.5.1)
132
145
  google-gax (~> 1.8)
133
146
  googleapis-common-protos (>= 1.3.9, < 2.0)
@@ -139,22 +152,22 @@ GEM
139
152
  googleauth (~> 0.9)
140
153
  grpc (~> 1.24)
141
154
  rly (~> 0.2.3)
142
- google-protobuf (3.15.7)
155
+ google-protobuf (3.17.3)
143
156
  googleapis-common-protos (1.3.11)
144
157
  google-protobuf (~> 3.14)
145
158
  googleapis-common-protos-types (>= 1.0.6, < 2.0)
146
159
  grpc (~> 1.27)
147
- googleapis-common-protos-types (1.0.6)
160
+ googleapis-common-protos-types (1.1.0)
148
161
  google-protobuf (~> 3.14)
149
- googleauth (0.16.1)
162
+ googleauth (0.17.0)
150
163
  faraday (>= 0.17.3, < 2.0)
151
164
  jwt (>= 1.4, < 3.0)
152
165
  memoist (~> 0.16)
153
166
  multi_json (~> 1.11)
154
167
  os (>= 0.9, < 2.0)
155
168
  signet (~> 0.14)
156
- grpc (1.36.0)
157
- google-protobuf (~> 3.14)
169
+ grpc (1.38.0)
170
+ google-protobuf (~> 3.15)
158
171
  googleapis-common-protos-types (~> 1.0)
159
172
  grpc-google-iam-v1 (0.6.11)
160
173
  google-protobuf (~> 3.14)
@@ -164,8 +177,8 @@ GEM
164
177
  i18n (1.8.10)
165
178
  concurrent-ruby (~> 1.0)
166
179
  jaro_winkler (1.5.4)
167
- jwt (2.2.2)
168
- loofah (2.9.0)
180
+ jwt (2.2.3)
181
+ loofah (2.12.0)
169
182
  crass (~> 1.0.2)
170
183
  nokogiri (>= 1.5.9)
171
184
  mail (2.7.1)
@@ -178,25 +191,25 @@ GEM
178
191
  nokogiri (~> 1)
179
192
  rake
180
193
  mini_mime (1.1.0)
181
- mini_portile2 (2.5.0)
194
+ mini_portile2 (2.6.1)
182
195
  minitest (5.14.4)
183
196
  multi_json (1.15.0)
184
197
  multipart-post (2.1.1)
185
- nio4r (2.5.7)
186
- nokogiri (1.11.2)
187
- mini_portile2 (~> 2.5.0)
198
+ nio4r (2.5.8)
199
+ nokogiri (1.12.3)
200
+ mini_portile2 (~> 2.6.1)
188
201
  racc (~> 1.4)
189
- octokit (4.20.0)
202
+ octokit (4.21.0)
190
203
  faraday (>= 0.9)
191
204
  sawyer (~> 0.8.0, >= 0.5.3)
192
205
  os (1.1.1)
193
206
  parallel (1.20.1)
194
- parser (3.0.0.0)
207
+ parser (3.0.2.0)
195
208
  ast (~> 2.4.1)
196
209
  protocol-hpack (1.4.2)
197
- protocol-http (0.21.0)
198
- protocol-http1 (0.13.2)
199
- protocol-http (~> 0.19)
210
+ protocol-http (0.22.5)
211
+ protocol-http1 (0.14.1)
212
+ protocol-http (~> 0.22)
200
213
  protocol-http2 (0.14.2)
201
214
  protocol-hpack (~> 1.4)
202
215
  protocol-http (~> 0.18)
@@ -224,7 +237,7 @@ GEM
224
237
  rails-dom-testing (2.0.3)
225
238
  activesupport (>= 4.2.0)
226
239
  nokogiri (>= 1.6)
227
- rails-html-sanitizer (1.3.0)
240
+ rails-html-sanitizer (1.4.1)
228
241
  loofah (~> 2.3)
229
242
  railties (6.0.0)
230
243
  actionpack (= 6.0.0)
@@ -233,8 +246,8 @@ GEM
233
246
  rake (>= 0.8.7)
234
247
  thor (>= 0.20.3, < 2.0)
235
248
  rainbow (3.0.0)
236
- rake (13.0.3)
237
- redis (4.2.5)
249
+ rake (13.0.6)
250
+ redis (4.4.0)
238
251
  retriable (3.1.2)
239
252
  rexml (3.2.5)
240
253
  rly (0.2.3)
@@ -251,7 +264,7 @@ GEM
251
264
  rspec-mocks (3.10.2)
252
265
  diff-lcs (>= 1.2.0, < 2.0)
253
266
  rspec-support (~> 3.10.0)
254
- rspec-rails (5.0.1)
267
+ rspec-rails (5.0.2)
255
268
  actionpack (>= 5.2)
256
269
  activesupport (>= 5.2)
257
270
  railties (>= 5.2)
@@ -270,11 +283,11 @@ GEM
270
283
  rubocop-rspec (1.37.0)
271
284
  rubocop (>= 0.68.1)
272
285
  ruby-progressbar (1.11.0)
273
- ruby2_keywords (0.0.4)
286
+ ruby2_keywords (0.0.5)
274
287
  sawyer (0.8.2)
275
288
  addressable (>= 2.3.5)
276
289
  faraday (> 0.8, < 2.0)
277
- semantic_logger (4.7.4)
290
+ semantic_logger (4.8.2)
278
291
  concurrent-ruby (~> 1.0)
279
292
  signet (0.15.0)
280
293
  addressable (~> 2.3)
@@ -296,11 +309,11 @@ GEM
296
309
  tzinfo (1.2.9)
297
310
  thread_safe (~> 0.1)
298
311
  unicode-display_width (1.6.1)
299
- webmock (3.12.2)
300
- addressable (>= 2.3.6)
312
+ webmock (3.14.0)
313
+ addressable (>= 2.8.0)
301
314
  crack (>= 0.3.2)
302
315
  hashdiff (>= 0.4.0, < 2.0.0)
303
- websocket-driver (0.7.3)
316
+ websocket-driver (0.7.5)
304
317
  websocket-extensions (>= 0.1.0)
305
318
  websocket-extensions (0.1.5)
306
319
  zeitwerk (2.4.2)
@@ -39,7 +39,7 @@ module Cloudtasker
39
39
  def self.all
40
40
  if redis.exists?(key)
41
41
  # Use Schedule Set if available
42
- redis.smembers(key).map { |id| find(id) }
42
+ redis.smembers(key).map { |id| find(id) }.compact
43
43
  else
44
44
  # Fallback to redis key matching and migrate tasks
45
45
  # to use Task Set instead.
@@ -6,7 +6,7 @@ module Cloudtasker
6
6
  # Include batch related methods onto Cloudtasker::Worker
7
7
  # See: Cloudtasker::Batch::Middleware#configure
8
8
  module Worker
9
- attr_accessor :batch
9
+ attr_accessor :batch, :parent_batch
10
10
  end
11
11
  end
12
12
  end
@@ -73,8 +73,12 @@ module Cloudtasker
73
73
  # Load extension if not loaded already on the worker class
74
74
  worker.class.include(Extension::Worker) unless worker.class <= Extension::Worker
75
75
 
76
- # Add batch capability
76
+ # Add batch and parent batch to worker
77
77
  worker.batch = new(worker)
78
+ worker.parent_batch = worker.batch.parent_batch
79
+
80
+ # Return the batch
81
+ worker.batch
78
82
  end
79
83
 
80
84
  #
@@ -259,9 +263,7 @@ module Cloudtasker
259
263
  migrate_batch_state_to_redis_hash
260
264
 
261
265
  # Update the batch state batch_id entry with the new status
262
- redis.with_lock("#{batch_state_gid}/#{batch_id}", max_wait: BATCH_MAX_LOCK_WAIT) do
263
- redis.hset(batch_state_gid, batch_id, status) if redis.hexists(batch_state_gid, batch_id)
264
- end
266
+ redis.hset(batch_state_gid, batch_id, status) if redis.hexists(batch_state_gid, batch_id)
265
267
  end
266
268
 
267
269
  #
@@ -273,10 +275,7 @@ module Cloudtasker
273
275
  migrate_batch_state_to_redis_hash
274
276
 
275
277
  # Check that all child jobs have completed
276
- redis.with_lock(batch_state_gid, max_wait: BATCH_MAX_LOCK_WAIT) do
277
- # Check that all children are complete
278
- redis.hvals(batch_state_gid).all? { |e| COMPLETION_STATUSES.include?(e) }
279
- end
278
+ redis.hvals(batch_state_gid).all? { |e| COMPLETION_STATUSES.include?(e) }
280
279
  end
281
280
 
282
281
  #
@@ -311,8 +310,8 @@ module Cloudtasker
311
310
  # Propagate event
312
311
  parent_batch&.on_child_complete(self, status)
313
312
 
314
- # The batch tree is complete. Cleanup the tree.
315
- cleanup unless parent_batch
313
+ # The batch tree is complete. Cleanup the downstream tree.
314
+ cleanup
316
315
  end
317
316
 
318
317
  #
@@ -427,8 +426,11 @@ module Cloudtasker
427
426
  # Perform job
428
427
  yield
429
428
 
430
- # Save batch (if child workers have been enqueued)
431
- setup
429
+ # Save batch if child jobs added
430
+ setup if jobs.any?
431
+
432
+ # Save parent batch if batch expanded
433
+ parent_batch&.setup if parent_batch&.jobs&.any?
432
434
 
433
435
  # Complete batch
434
436
  complete(:completed)
@@ -7,7 +7,8 @@ module Cloudtasker
7
7
  class Config
8
8
  attr_accessor :redis, :store_payloads_in_redis
9
9
  attr_writer :secret, :gcp_location_id, :gcp_project_id,
10
- :gcp_queue_prefix, :processor_path, :logger, :mode, :max_retries, :dispatch_deadline
10
+ :gcp_queue_prefix, :processor_path, :logger, :mode, :max_retries,
11
+ :dispatch_deadline, :on_error, :on_dead
11
12
 
12
13
  # Max Cloud Task size in bytes
13
14
  MAX_TASK_SIZE = 100 * 1024 # 100 KB
@@ -51,6 +52,9 @@ module Cloudtasker
51
52
  MIN_DISPATCH_DEADLINE = 15 # seconds
52
53
  MAX_DISPATCH_DEADLINE = 30 * 60 # 30 minutes
53
54
 
55
+ # Default on_error Proc
56
+ DEFAULT_ON_ERROR = ->(error, worker) {}
57
+
54
58
  # The number of times jobs will be attempted before declaring them dead.
55
59
  #
56
60
  # With the default retry configuration (maxDoublings = 16 and minBackoff = 0.100s)
@@ -229,11 +233,31 @@ module Cloudtasker
229
233
  # @return [String] The cloudtasker secret
230
234
  #
231
235
  def secret
232
- @secret || (
236
+ @secret ||= (
233
237
  defined?(Rails) && Rails.application.credentials&.dig(:secret_key_base)
234
238
  ) || raise(StandardError, SECRET_MISSING_ERROR)
235
239
  end
236
240
 
241
+ #
242
+ # Return a Proc invoked whenever a worker runtime error is raised.
243
+ # See Cloudtasker::WorkerHandler.with_worker_handling
244
+ #
245
+ # @return [Proc] A Proc handler
246
+ #
247
+ def on_error
248
+ @on_error || DEFAULT_ON_ERROR
249
+ end
250
+
251
+ #
252
+ # Return a Proc invoked whenever a worker DeadWorkerError is raised.
253
+ # See Cloudtasker::WorkerHandler.with_worker_handling
254
+ #
255
+ # @return [Proc] A Proc handler
256
+ #
257
+ def on_dead
258
+ @on_dead || DEFAULT_ON_ERROR
259
+ end
260
+
237
261
  #
238
262
  # Return the chain of client middlewares.
239
263
  #
@@ -84,7 +84,7 @@ module Cloudtasker
84
84
 
85
85
  # Deliver task
86
86
  begin
87
- Thread.current['task'].deliver
87
+ Thread.current['task']&.deliver
88
88
  rescue Errno::EBADF, Errno::ECONNREFUSED => e
89
89
  raise(e) unless Thread.current['attempts'] < 3
90
90
 
@@ -149,25 +149,18 @@ module Cloudtasker
149
149
  # if taken by another job.
150
150
  #
151
151
  def lock!
152
- redis.with_lock(unique_gid) do
153
- locked_id = redis.get(unique_gid)
152
+ lock_acquired = redis.set(unique_gid, id, nx: true, ex: lock_ttl)
153
+ lock_already_acquired = !lock_acquired && redis.get(unique_gid) == id
154
154
 
155
- # Abort job lock process if lock is already taken by another job
156
- raise(LockError, locked_id) if locked_id && locked_id != id
157
-
158
- # Take job lock if the lock is currently free
159
- redis.set(unique_gid, id, ex: lock_ttl) unless locked_id
160
- end
155
+ raise(LockError) unless lock_acquired || lock_already_acquired
161
156
  end
162
157
 
163
158
  #
164
159
  # Delete the job lock.
165
160
  #
166
161
  def unlock!
167
- redis.with_lock(unique_gid) do
168
- locked_id = redis.get(unique_gid)
169
- redis.del(unique_gid) if locked_id == id
170
- end
162
+ locked_id = redis.get(unique_gid)
163
+ redis.del(unique_gid) if locked_id == id
171
164
  end
172
165
  end
173
166
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cloudtasker
4
- VERSION = '0.12.rc8'
4
+ VERSION = '0.12.0'
5
5
  end
@@ -14,12 +14,6 @@ module Cloudtasker
14
14
  # payloads in Redis
15
15
  REDIS_PAYLOAD_NAMESPACE = 'payload'
16
16
 
17
- # Arg payload cache keys get expired instead of deleted
18
- # in case jobs are re-processed due to connection interruption
19
- # (job is successful but Cloud Task considers it as failed due
20
- # to network interruption)
21
- ARGS_PAYLOAD_CLEANUP_TTL = 3600 # 1 hour
22
-
23
17
  #
24
18
  # Return a namespaced key
25
19
  #
@@ -100,20 +94,19 @@ module Cloudtasker
100
94
  # Yied worker
101
95
  resp = yield(worker)
102
96
 
103
- # Schedule args payload deletion after job has been successfully processed
104
- # Note: we expire the key instead of deleting it immediately in case the job
105
- # succeeds but is considered as failed by Cloud Task due to network interruption.
106
- # In such case the job is likely to be re-processed soon after.
107
- redis.expire(args_payload_key, ARGS_PAYLOAD_CLEANUP_TTL) if args_payload_key && !worker.job_reenqueued
97
+ # Delete stored args payload if job has completed
98
+ redis.del(args_payload_key) if args_payload_key && !worker.job_reenqueued
108
99
 
109
100
  resp
110
101
  rescue DeadWorkerError => e
111
102
  # Delete stored args payload if job is dead
112
- redis.expire(args_payload_key, ARGS_PAYLOAD_CLEANUP_TTL) if args_payload_key
103
+ redis.del(args_payload_key) if args_payload_key
113
104
  log_execution_error(worker, e)
105
+ Cloudtasker.config.on_dead.call(e, worker)
114
106
  raise(e)
115
107
  rescue StandardError => e
116
108
  log_execution_error(worker, e)
109
+ Cloudtasker.config.on_error.call(e, worker)
117
110
  raise(e)
118
111
  end
119
112