cloudtasker 0.12.rc8 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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