job-iteration 1.15.0 → 1.16.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 +4 -4
- data/CHANGELOG.md +14 -1
- data/lib/job-iteration/enumerator_builder.rb +9 -2
- data/lib/job-iteration/interruption_adapters.rb +3 -2
- data/lib/job-iteration/iteration.rb +1 -1
- data/lib/job-iteration/log_subscriber.rb +9 -1
- data/lib/job-iteration/parallel_enumerator.rb +27 -6
- data/lib/job-iteration/version.rb +1 -1
- data/lib/job-iteration.rb +10 -2
- data/lib/tapioca/dsl/compilers/job_iteration.rb +3 -1
- 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: ac8363ff51f4445e2f029536f0b6bceac11197a95c985bb0f68077850c446db8
|
|
4
|
+
data.tar.gz: 2dfac082f347b7c330ba8122ab87f5f28cdb9a560d1b20cb87af1e7690aeed02
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9fec07c8529f102047083a54cac23a789822f295f01edf39cd360e12048f2ab0a0dfef277a9cb0b04ded0795635a178b44223ee634f3b981fe8cebddec55d6a8
|
|
7
|
+
data.tar.gz: 76aaf31360925043bf01f815156304b64b9e732d1a5d803bd41c92ddcbacfbd9dbcc0bbc79b5e4a0a34db23f77b33e1c5adaf5bf71f8b1ea942034ad47b4c19c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
## Main (unreleased)
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
### Breaking Changes
|
|
4
4
|
|
|
5
5
|
nil
|
|
@@ -16,6 +16,19 @@ nil
|
|
|
16
16
|
|
|
17
17
|
nil
|
|
18
18
|
|
|
19
|
+
## v1.16.0 (Oct 20, 2026)
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
- [746](https://github.com/Shopify/job-iteration/pull/746) - `build_parallel_enumerator` accepts `tolerated_enqueue_errors:`, a list of enqueue error classes that do not fail the parent job. A child job that fails to enqueue with one of them is skipped and reported through the `skipped_parallel_jobs.iteration` event.
|
|
24
|
+
- [747](https://github.com/Shopify/job-iteration/pull/747) - Support running iteration jobs in non-main Ractors. `JobIteration.max_job_runtime=` and `JobIteration.default_retry_backoff=` now make their values Ractor-shareable, which deep-freezes them.
|
|
25
|
+
|
|
26
|
+
## v1.15.1 (Sep 15, 2026)
|
|
27
|
+
|
|
28
|
+
### Bug fixes
|
|
29
|
+
|
|
30
|
+
- [734](https://github.com/Shopify/job-iteration/pull/734) - Account for anonymous block parameters in DSL compiler.
|
|
31
|
+
|
|
19
32
|
## v1.15.0 (Jun 4, 2026)
|
|
20
33
|
|
|
21
34
|
### Features
|
|
@@ -233,12 +233,19 @@ module JobIteration
|
|
|
233
233
|
wrap(self, enum)
|
|
234
234
|
end
|
|
235
235
|
|
|
236
|
-
def build_parallel_enumerator(instances:, cursor:, &block)
|
|
236
|
+
def build_parallel_enumerator(instances:, cursor:, tolerated_enqueue_errors: [], &block)
|
|
237
237
|
unless instances.is_a?(Integer) && instances.positive?
|
|
238
238
|
raise ArgumentError, "instances must be a positive Integer"
|
|
239
239
|
end
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
unless tolerated_enqueue_errors.is_a?(Array) &&
|
|
242
|
+
tolerated_enqueue_errors.all? { |error_class| error_class.is_a?(Class) && error_class <= Exception }
|
|
243
|
+
raise ArgumentError, "tolerated_enqueue_errors must be an Array of Exception classes"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if cursor.nil?
|
|
247
|
+
return ParallelEnumerator::EnqueueJobs.new(instances, tolerated_enqueue_errors: tolerated_enqueue_errors)
|
|
248
|
+
end
|
|
242
249
|
|
|
243
250
|
enum = ParallelEnumerator.new(block, cursor: cursor).to_enum
|
|
244
251
|
wrap(self, enum)
|
|
@@ -34,7 +34,8 @@ module JobIteration
|
|
|
34
34
|
def register(name, adapter)
|
|
35
35
|
raise ArgumentError, "adapter must be callable" unless adapter.respond_to?(:call)
|
|
36
36
|
|
|
37
|
-
registry
|
|
37
|
+
@registry = registry.merge(name.to_sym => adapter).freeze
|
|
38
|
+
nil
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
private
|
|
@@ -42,7 +43,7 @@ module JobIteration
|
|
|
42
43
|
attr_reader :registry
|
|
43
44
|
end
|
|
44
45
|
|
|
45
|
-
@registry = {}
|
|
46
|
+
@registry = {}.freeze
|
|
46
47
|
|
|
47
48
|
# Built-in Rails adapters. It doesn't make sense to interrupt for these.
|
|
48
49
|
register(:async, NullAdapter)
|
|
@@ -345,7 +345,7 @@ module JobIteration
|
|
|
345
345
|
def method_parameters(method_name)
|
|
346
346
|
method = method(method_name)
|
|
347
347
|
|
|
348
|
-
if defined?(T::Private::Methods)
|
|
348
|
+
if defined?(T::Private::Methods) && Ractor.current == Ractor.main
|
|
349
349
|
signature = T::Private::Methods.signature_for_method(method)
|
|
350
350
|
method = signature.method if signature
|
|
351
351
|
end
|
|
@@ -21,7 +21,15 @@ module JobIteration
|
|
|
21
21
|
|
|
22
22
|
def enqueue_parallel_jobs(event)
|
|
23
23
|
info do
|
|
24
|
-
"[JobIteration::Iteration]
|
|
24
|
+
"[JobIteration::Iteration] Attempted to enqueue #{event.payload[:instances]} parallel jobs."
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def skipped_parallel_jobs(event)
|
|
29
|
+
info do
|
|
30
|
+
"[JobIteration::Iteration] Skipped #{event.payload[:skipped_instances].size} of " \
|
|
31
|
+
"#{event.payload[:instances]} parallel jobs that failed to enqueue with a tolerated error. " \
|
|
32
|
+
"skipped_instances=#{event.payload[:skipped_instances]} enqueue_errors=#{event.payload[:enqueue_errors].uniq}"
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
|
|
@@ -7,11 +7,12 @@ module JobIteration
|
|
|
7
7
|
class EnqueueError < StandardError; end
|
|
8
8
|
|
|
9
9
|
class EnqueueJobs
|
|
10
|
-
def initialize(instances)
|
|
10
|
+
def initialize(instances, tolerated_enqueue_errors: [])
|
|
11
11
|
@instances = instances
|
|
12
|
+
@tolerated_enqueue_errors = tolerated_enqueue_errors
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
attr_reader :instances
|
|
15
|
+
attr_reader :instances, :tolerated_enqueue_errors
|
|
15
16
|
|
|
16
17
|
def enqueue_jobs(job)
|
|
17
18
|
child_jobs = instances.times.map do |index|
|
|
@@ -26,10 +27,30 @@ module JobIteration
|
|
|
26
27
|
|
|
27
28
|
ActiveJob.perform_all_later(child_jobs)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
failed_jobs = child_jobs.reject(&:successfully_enqueued?)
|
|
31
|
+
return if failed_jobs.empty?
|
|
32
|
+
|
|
33
|
+
skipped_jobs, failed_jobs = failed_jobs.partition { |child_job| tolerated?(child_job.enqueue_error) }
|
|
34
|
+
instrument_skipped_jobs(job, skipped_jobs) if skipped_jobs.any?
|
|
35
|
+
return if failed_jobs.empty?
|
|
36
|
+
|
|
37
|
+
raise EnqueueError, "Failed to enqueue #{failed_jobs.size} out of #{instances} child jobs"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def tolerated?(enqueue_error)
|
|
43
|
+
tolerated_enqueue_errors.any? { |error_class| enqueue_error.is_a?(error_class) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def instrument_skipped_jobs(job, skipped_jobs)
|
|
47
|
+
ActiveSupport::Notifications.instrument(
|
|
48
|
+
"skipped_parallel_jobs.iteration",
|
|
49
|
+
job_class: job.class.name,
|
|
50
|
+
instances: instances,
|
|
51
|
+
skipped_instances: skipped_jobs.map { |child_job| child_job.cursor_position.fetch("instance") },
|
|
52
|
+
enqueue_errors: skipped_jobs.map { |child_job| child_job.enqueue_error.class.name },
|
|
53
|
+
)
|
|
33
54
|
end
|
|
34
55
|
end
|
|
35
56
|
|
data/lib/job-iteration.rb
CHANGED
|
@@ -45,7 +45,11 @@ module JobIteration
|
|
|
45
45
|
# class ChildJob < MyJob
|
|
46
46
|
# self.job_iteration_max_job_runtime = 3.minutes # MyJob's 1.minute will be discarded.
|
|
47
47
|
# # ...
|
|
48
|
-
|
|
48
|
+
attr_reader :max_job_runtime
|
|
49
|
+
|
|
50
|
+
def max_job_runtime=(max_job_runtime)
|
|
51
|
+
@max_job_runtime = Ractor.make_shareable(max_job_runtime)
|
|
52
|
+
end
|
|
49
53
|
|
|
50
54
|
# Configures a delay duration to wait before resuming an interrupted job.
|
|
51
55
|
# @example
|
|
@@ -55,7 +59,11 @@ module JobIteration
|
|
|
55
59
|
# Defaults to nil which means interrupted jobs will be retried immediately.
|
|
56
60
|
# This value will be ignored when an interruption is raised by a throttle enumerator,
|
|
57
61
|
# where the throttle backoff value will take precedence over this setting.
|
|
58
|
-
|
|
62
|
+
attr_reader :default_retry_backoff
|
|
63
|
+
|
|
64
|
+
def default_retry_backoff=(default_retry_backoff)
|
|
65
|
+
@default_retry_backoff = Ractor.make_shareable(default_retry_backoff)
|
|
66
|
+
end
|
|
59
67
|
|
|
60
68
|
attr_reader :interruption_adapter
|
|
61
69
|
|
|
@@ -37,7 +37,9 @@ module Tapioca
|
|
|
37
37
|
if signature
|
|
38
38
|
fixed_hash_args = signature.arg_types.select { |arg_type| T::Types::FixedHash === arg_type[1] }.to_h
|
|
39
39
|
expanded_parameters = parameters.flat_map do |typed_param|
|
|
40
|
-
|
|
40
|
+
param_name = typed_param.param.name
|
|
41
|
+
|
|
42
|
+
if param_name && (hash_type = fixed_hash_args[param_name.to_sym])
|
|
41
43
|
expand_fixed_hash(typed_param, hash_type)
|
|
42
44
|
else
|
|
43
45
|
typed_param
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: job-iteration
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shopify
|
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
79
|
version: '0'
|
|
80
80
|
requirements: []
|
|
81
|
-
rubygems_version: 4.0.
|
|
81
|
+
rubygems_version: 4.0.22
|
|
82
82
|
specification_version: 4
|
|
83
83
|
summary: Makes your background jobs interruptible and resumable.
|
|
84
84
|
test_files: []
|