cloudtasker 0.12.rc1 → 0.12.rc6
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 +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +4 -2
- data/README.md +2 -2
- data/app/controllers/cloudtasker/worker_controller.rb +1 -1
- data/docs/BATCH_JOBS.md +24 -3
- data/lib/cloudtasker/backend/redis_task.rb +1 -0
- data/lib/cloudtasker/batch/job.rb +12 -6
- data/lib/cloudtasker/version.rb +1 -1
- data/lib/cloudtasker/worker.rb +17 -6
- data/lib/cloudtasker/worker_handler.rb +2 -2
- data/lib/cloudtasker/worker_logger.rb +29 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00be8d2e572e3129ef5330cdbb555aa0485edd15b191d5d8ccc5d28a98a0eab8
|
4
|
+
data.tar.gz: 79067d7953556f03e9a81ee9d977cf2734a9843df8f9a4613dbd8d81274bc599
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99d9b7ba99390e6bc10e9ed917262782a7c47814df6412c847dedd06a8a06a6b9ad4eaf5ddbcde03534ec88f34fcb1cda03621a3c2a0202031d087e6cd915c5f
|
7
|
+
data.tar.gz: e15a6a167f15dbd8c390c7d393a80297b6cd805c85d4e5ca16f961ff147c909bebbfcf9c326708e7d7508caa3ff47b726f071e618e3979a1340848420311ddbb
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## Latest RC [v0.12.
|
3
|
+
## Latest RC [v0.12.rc6](https://github.com/keypup-io/cloudtasker/tree/v0.12.rc6) (2021-03-31)
|
4
4
|
|
5
|
-
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.11.0...v0.12.
|
5
|
+
[Full Changelog](https://github.com/keypup-io/cloudtasker/compare/v0.11.0...v0.12.rc6)
|
6
6
|
|
7
7
|
**Improvements:**
|
8
8
|
- ActiveJob: do not double log errors (ActiveJob has its own error logging)
|
@@ -10,6 +10,8 @@
|
|
10
10
|
- Error logging: Do not log exception and stack trace separately, combine them instead.
|
11
11
|
- Batch callbacks: Retry jobs when completion callback fails
|
12
12
|
- Redis: Use Redis Sets instead of key pattern matching for listing methods (Cron jobs and Local Server)
|
13
|
+
- Batch progress: restrict calculation to direct children by default. Allow depth to be specified. Calculating progress using all tree jobs created significant delays on large batches.
|
14
|
+
- Worker: raise DeadWorkerError instead of MissingWorkerArgumentsError when arguments are missing. This is more consistent with what middlewares expect.
|
13
15
|
|
14
16
|
**Fixed bugs:**
|
15
17
|
- Retries: Enforce job retry limit on job processing. There was an edge case where jobs could be retried indefinitely on batch callback errors.
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ 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).
|
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). We are planning to release the official `v1.0.0` somewhere in 2021, in case we've missed any edge-case bug.
|
16
16
|
|
17
17
|
## Summary
|
18
18
|
|
@@ -136,7 +136,7 @@ That's it! Your job was picked up by the Cloudtasker local server and sent for p
|
|
136
136
|
Now jump to the next section to configure your app to use Google Cloud Tasks as a backend.
|
137
137
|
|
138
138
|
## Get started with Rails & ActiveJob
|
139
|
-
**Note**: ActiveJob is supported since `0.11.0`
|
139
|
+
**Note**: ActiveJob is supported since `0.11.0`
|
140
140
|
**Note**: Cloudtasker extensions (cron, batch and unique jobs) are not available when using cloudtasker via ActiveJob.
|
141
141
|
|
142
142
|
Cloudtasker is pre-integrated with ActiveJob. Follow the steps below to get started.
|
@@ -19,7 +19,7 @@ module Cloudtasker
|
|
19
19
|
# Process payload
|
20
20
|
WorkerHandler.execute_from_payload!(payload)
|
21
21
|
head :no_content
|
22
|
-
rescue DeadWorkerError
|
22
|
+
rescue DeadWorkerError
|
23
23
|
# 205: job will NOT be retried
|
24
24
|
head :reset_content
|
25
25
|
rescue InvalidWorkerError
|
data/docs/BATCH_JOBS.md
CHANGED
@@ -84,8 +84,29 @@ You can access progression statistics in callback using `batch.progress`. See th
|
|
84
84
|
E.g.
|
85
85
|
```ruby
|
86
86
|
def on_batch_node_complete(_child_job)
|
87
|
-
|
88
|
-
logger.info("
|
89
|
-
logger.info("
|
87
|
+
progress = batch.progress
|
88
|
+
logger.info("Total: #{progress.total}")
|
89
|
+
logger.info("Completed: #{progress.completed}")
|
90
|
+
logger.info("Progress: #{progress.percent.to_i}%")
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
**Since:** `v0.12.rc5`
|
95
|
+
By default the `progress` method only considers the direct child jobs to evaluate the batch progress. You can pass `depth: somenumber` to the `progress` method to calculate the actual batch progress in a more granular way. Be careful however that this method recursively calculates progress on the sub-batches and is therefore expensive.
|
96
|
+
|
97
|
+
E.g.
|
98
|
+
```ruby
|
99
|
+
def on_batch_node_complete(_child_job)
|
100
|
+
# Considers the children for batch progress calculation
|
101
|
+
progress_0 = batch.progress # same as batch.progress(depth: 0)
|
102
|
+
|
103
|
+
# Considers the children and grand-children for batch progress calculation
|
104
|
+
progress_1 = batch.progress(depth: 1)
|
105
|
+
|
106
|
+
# Considers the children, grand-children and grand-grand-children for batch progress calculation
|
107
|
+
progress_2 = batch.progress(depth: 3)
|
108
|
+
|
109
|
+
logger.info("Progress: #{progress_1.percent.to_i}%")
|
110
|
+
logger.info("Progress: #{progress_2.percent.to_i}%")
|
90
111
|
end
|
91
112
|
```
|
@@ -271,9 +271,8 @@ module Cloudtasker
|
|
271
271
|
raise(e) unless IGNORED_ERRORED_CALLBACKS.include?(callback)
|
272
272
|
|
273
273
|
# Log error instead
|
274
|
-
worker.logger.error(
|
275
|
-
|
276
|
-
)
|
274
|
+
worker.logger.error(e)
|
275
|
+
worker.logger.error("Callback #{callback} failed to run. Skipping to preserve error flow.")
|
277
276
|
end
|
278
277
|
|
279
278
|
#
|
@@ -348,13 +347,20 @@ module Cloudtasker
|
|
348
347
|
#
|
349
348
|
# @return [Cloudtasker::Batch::BatchProgress] The batch progress.
|
350
349
|
#
|
351
|
-
def progress
|
350
|
+
def progress(depth: 0)
|
351
|
+
depth = depth.to_i
|
352
|
+
|
352
353
|
# Capture batch state
|
353
354
|
state = batch_state
|
354
355
|
|
355
|
-
#
|
356
|
+
# Return immediately if we do not need to go down the tree
|
357
|
+
return BatchProgress.new(state) if depth <= 0
|
358
|
+
|
359
|
+
# Sum batch progress of current batch and sub-batches up to the specified
|
360
|
+
# depth
|
356
361
|
state.to_h.reduce(BatchProgress.new(state)) do |memo, (child_id, child_status)|
|
357
|
-
memo + (self.class.find(child_id)&.progress
|
362
|
+
memo + (self.class.find(child_id)&.progress(depth: depth - 1) ||
|
363
|
+
BatchProgress.new(child_id => child_status))
|
358
364
|
end
|
359
365
|
end
|
360
366
|
|
data/lib/cloudtasker/version.rb
CHANGED
data/lib/cloudtasker/worker.rb
CHANGED
@@ -332,6 +332,22 @@ module Cloudtasker
|
|
332
332
|
job_retries > job_max_retries
|
333
333
|
end
|
334
334
|
|
335
|
+
#
|
336
|
+
# Return true if the job arguments are missing.
|
337
|
+
#
|
338
|
+
# This may happen if a job
|
339
|
+
# was successfully run but retried due to Cloud Task dispatch deadline
|
340
|
+
# exceeded. If the arguments were stored in Redis then they may have
|
341
|
+
# been flushed already after the successful completion.
|
342
|
+
#
|
343
|
+
# If job arguments are missing then the job will simply be declared dead.
|
344
|
+
#
|
345
|
+
# @return [Boolean] True if the arguments are missing.
|
346
|
+
#
|
347
|
+
def arguments_missing?
|
348
|
+
job_args.empty? && [0, -1].exclude?(method(:perform).arity)
|
349
|
+
end
|
350
|
+
|
335
351
|
#
|
336
352
|
# Return the time taken (in seconds) to perform the job. This duration
|
337
353
|
# includes the middlewares and the actual perform method.
|
@@ -384,14 +400,9 @@ module Cloudtasker
|
|
384
400
|
Cloudtasker.config.server_middleware.invoke(self) do
|
385
401
|
# Immediately abort the job if it is already dead
|
386
402
|
flag_as_dead if job_dead?
|
403
|
+
flag_as_dead(MissingWorkerArgumentsError.new('worker arguments are missing')) if arguments_missing?
|
387
404
|
|
388
405
|
begin
|
389
|
-
# Abort if arguments are missing. This may happen with redis arguments storage
|
390
|
-
# if Cloud Tasks times out on a job but the job still succeeds
|
391
|
-
if job_args.empty? && [0, -1].exclude?(method(:perform).arity)
|
392
|
-
raise(MissingWorkerArgumentsError, 'worker arguments are missing')
|
393
|
-
end
|
394
|
-
|
395
406
|
# Perform the job
|
396
407
|
perform(*job_args)
|
397
408
|
rescue StandardError => e
|
@@ -64,7 +64,7 @@ module Cloudtasker
|
|
64
64
|
logger = worker&.logger || Cloudtasker.logger
|
65
65
|
|
66
66
|
# Log error
|
67
|
-
logger.error(
|
67
|
+
logger.error(error)
|
68
68
|
end
|
69
69
|
|
70
70
|
#
|
@@ -107,7 +107,7 @@ module Cloudtasker
|
|
107
107
|
redis.expire(args_payload_key, ARGS_PAYLOAD_CLEANUP_TTL) if args_payload_key && !worker.job_reenqueued
|
108
108
|
|
109
109
|
resp
|
110
|
-
rescue DeadWorkerError
|
110
|
+
rescue DeadWorkerError => e
|
111
111
|
# Delete stored args payload if job is dead
|
112
112
|
redis.expire(args_payload_key, ARGS_PAYLOAD_CLEANUP_TTL) if args_payload_key
|
113
113
|
log_execution_error(worker, e)
|
@@ -51,6 +51,26 @@ module Cloudtasker
|
|
51
51
|
Cloudtasker.logger
|
52
52
|
end
|
53
53
|
|
54
|
+
#
|
55
|
+
# Format the log message as string.
|
56
|
+
#
|
57
|
+
# @param [Object] msg The log message or object.
|
58
|
+
#
|
59
|
+
# @return [String] The formatted message
|
60
|
+
#
|
61
|
+
def formatted_message_as_string(msg)
|
62
|
+
# Format message
|
63
|
+
msg_content = if msg.is_a?(Exception)
|
64
|
+
[msg.inspect, msg.backtrace].flatten(1).join("\n")
|
65
|
+
elsif msg.is_a?(String)
|
66
|
+
msg
|
67
|
+
else
|
68
|
+
msg.inspect
|
69
|
+
end
|
70
|
+
|
71
|
+
"[Cloudtasker][#{worker.class}][#{worker.job_id}] #{msg_content}"
|
72
|
+
end
|
73
|
+
|
54
74
|
#
|
55
75
|
# Format main log message.
|
56
76
|
#
|
@@ -59,7 +79,12 @@ module Cloudtasker
|
|
59
79
|
# @return [String] The formatted log message
|
60
80
|
#
|
61
81
|
def formatted_message(msg)
|
62
|
-
|
82
|
+
if msg.is_a?(String)
|
83
|
+
formatted_message_as_string(msg)
|
84
|
+
else
|
85
|
+
# Delegate object formatting to logger
|
86
|
+
msg
|
87
|
+
end
|
63
88
|
end
|
64
89
|
|
65
90
|
#
|
@@ -147,7 +172,9 @@ module Cloudtasker
|
|
147
172
|
# ActiveSupport::Logger does not support passing a payload through a block on top
|
148
173
|
# of a message.
|
149
174
|
if defined?(ActiveSupport::Logger) && logger.is_a?(ActiveSupport::Logger)
|
150
|
-
logger
|
175
|
+
# The logger is fairly basic in terms of formatting. All inputs get converted
|
176
|
+
# as regular strings.
|
177
|
+
logger.send(level) { "#{formatted_message_as_string(msg)} -- #{payload_block.call}" }
|
151
178
|
else
|
152
179
|
logger.send(level, formatted_message(msg), &payload_block)
|
153
180
|
end
|
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.12.
|
4
|
+
version: 0.12.rc6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arnaud Lachaume
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|