Skip to content

Commit 0bfc557

Browse files
Merge pull request #427 from Shopify/use-rails-logger
Use the Rails application logger
2 parents bd21ef6 + a758bc3 commit 0bfc557

16 files changed

+125
-157
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
### Main (unreleased)
22

3-
- [367](https://github.com/Shopify/job-iteration/pull/367) - Iteration can use multiple Active Job backends simultaneously by inferring the interruption adapter from the job's `queue_adapter_name`. Iteration will now raise `JobIteration::Integrations::LoadError` if no interruption adapter is found for the job's queue adapter, instead of never interrupting the job. `JobIteration.interruption_adapter` and `.load_integrations` have been removed. `JobIteration::Integrations.register` has been added.
3+
## v1.4.1 (Sep 5, 2023)
4+
5+
### Bug fixes
6+
7+
- [427](https://github.com/Shopify/job-iteration/pull/427) - Use the Rails application logger. Changes from [338](https://github.com/Shopify/job-iteration/pull/338) resulted in logging to the original value of ActiveJob.logger, not the one configured by the Rails application.
48

59
## v1.4.0 (Aug 23, 2023)
610

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ GIT
77
PATH
88
remote: .
99
specs:
10-
job-iteration (1.4.0)
10+
job-iteration (1.4.1)
1111
activejob (>= 5.2)
1212

1313
GEM

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ end
152152
```
153153

154154
Iteration hooks into Sidekiq and Resque out of the box to support graceful interruption. No extra configuration is required.
155-
Adapters for other Active Job backends can be registered with `JobIteration::Integrations.register("my_queue_adapter_name", object)`, where `object` must implement the `call` method returning `true` if the job must be interrupted and `false` otherwise.
156155

157156
## Guides
158157

@@ -184,7 +183,7 @@ There a few configuration assumptions that are required for Iteration to work wi
184183

185184
**Why can't I just iterate in `#perform` method and do whatever I want?** You can, but then your job has to comply with a long list of requirements, such as the ones above. This creates leaky abstractions more easily, when instead we can expose a more powerful abstraction for developers--without exposing the underlying infrastructure.
186185

187-
**What happens when my job is interrupted?** A checkpoint will be persisted after the current `each_iteration`, and the job will be re-enqueued. Once it's popped off the queue, the worker will work off from the next iteration.
186+
**What happens when my job is interrupted?** A checkpoint will be persisted to Redis after the current `each_iteration`, and the job will be re-enqueued. Once it's popped off the queue, the worker will work off from the next iteration.
188187

189188
**What happens with retries?** An interruption of a job does not count as a retry. The iteration of job that caused the job to fail will be retried and progress will continue from there on.
190189

lib/job-iteration.rb

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22

33
require "active_job"
44
require_relative "./job-iteration/version"
5-
require_relative "./job-iteration/integrations"
65
require_relative "./job-iteration/enumerator_builder"
76
require_relative "./job-iteration/iteration"
87
require_relative "./job-iteration/log_subscriber"
98

109
module JobIteration
10+
IntegrationLoadError = Class.new(StandardError)
11+
12+
INTEGRATIONS = [:resque, :sidekiq]
13+
1114
extend self
1215

13-
attr_accessor :logger
16+
attr_writer :logger
1417

15-
self.logger = ActiveJob::Base.logger
18+
class << self
19+
def logger
20+
@logger || ActiveJob::Base.logger
21+
end
22+
end
1623

1724
# Use this to _always_ interrupt the job after it's been running for more than N seconds.
1825
# @example
@@ -42,6 +49,11 @@ module JobIteration
4249
# where the throttle backoff value will take precedence over this setting.
4350
attr_accessor :default_retry_backoff
4451

52+
# Used internally for hooking into job processing frameworks like Sidekiq and Resque.
53+
attr_accessor :interruption_adapter
54+
55+
self.interruption_adapter = -> { false }
56+
4557
# Set if you want to use your own enumerator builder instead of default EnumeratorBuilder.
4658
# @example
4759
#
@@ -53,4 +65,29 @@ module JobIteration
5365
attr_accessor :enumerator_builder
5466

5567
self.enumerator_builder = JobIteration::EnumeratorBuilder
68+
69+
def load_integrations
70+
loaded = nil
71+
INTEGRATIONS.each do |integration|
72+
load_integration(integration)
73+
if loaded
74+
raise IntegrationLoadError,
75+
"#{loaded} integration has already been loaded, but #{integration} is also available. " \
76+
"Iteration will only work with one integration."
77+
end
78+
loaded = integration
79+
rescue LoadError
80+
end
81+
end
82+
83+
def load_integration(integration)
84+
unless INTEGRATIONS.include?(integration)
85+
raise IntegrationLoadError,
86+
"#{integration} integration is not supported. Available integrations: #{INTEGRATIONS.join(", ")}"
87+
end
88+
89+
require_relative "./job-iteration/integrations/#{integration}"
90+
end
5691
end
92+
93+
JobIteration.load_integrations unless ENV["ITERATION_DISABLE_AUTOCONFIGURE"]

lib/job-iteration/active_record_enumerator.rb

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def records
3131
def batches
3232
cursor = finder_cursor
3333
Enumerator.new(method(:size)) do |yielder|
34-
while (records = instrument_next_batch(cursor))
34+
while (records = cursor.next_batch(@batch_size))
3535
yielder.yield(records, cursor_value(records.last)) if records.any?
3636
end
3737
end
@@ -43,12 +43,6 @@ def size
4343

4444
private
4545

46-
def instrument_next_batch(cursor)
47-
ActiveSupport::Notifications.instrument("active_record_cursor.iteration") do
48-
cursor.next_batch(@batch_size)
49-
end
50-
end
51-
5246
def cursor_value(record)
5347
positions = @columns.map do |column|
5448
attribute_name = column.to_s.split(".").last

lib/job-iteration/integrations.rb

Lines changed: 0 additions & 37 deletions
This file was deleted.

lib/job-iteration/integrations/resque.rb

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,21 @@
44

55
module JobIteration
66
module Integrations
7-
module Resque
8-
module IterationExtension
9-
def initialize(*)
10-
$resque_worker = self
11-
super
12-
end
13-
end
14-
15-
# @private
16-
module ::Resque
17-
class Worker
18-
# The patch is required in order to call shutdown? on a Resque::Worker instance
19-
prepend(IterationExtension)
20-
end
7+
module ResqueIterationExtension # @private
8+
def initialize(*) # @private
9+
$resque_worker = self
10+
super
2111
end
12+
end
2213

23-
class << self
24-
def call
25-
$resque_worker.try!(:shutdown?)
26-
end
14+
# @private
15+
module ::Resque
16+
class Worker
17+
# The patch is required in order to call shutdown? on a Resque::Worker instance
18+
prepend(ResqueIterationExtension)
2719
end
2820
end
21+
22+
JobIteration.interruption_adapter = -> { $resque_worker.try!(:shutdown?) }
2923
end
3024
end

lib/job-iteration/integrations/sidekiq.rb

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
# frozen_string_literal: true
22

3+
require "sidekiq"
4+
35
module JobIteration
4-
module Integrations
5-
module Sidekiq
6-
class << self
7-
def call
8-
if defined?(::Sidekiq::CLI) && (instance = ::Sidekiq::CLI.instance)
9-
instance.launcher.stopping?
10-
else
11-
false
12-
end
13-
end
6+
module Integrations # @private
7+
JobIteration.interruption_adapter = -> do
8+
if defined?(Sidekiq::CLI) && Sidekiq::CLI.instance
9+
Sidekiq::CLI.instance.launcher.stopping?
10+
else
11+
false
1412
end
1513
end
1614
end

lib/job-iteration/iteration.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,6 @@ def retry_job(*, **)
136136

137137
private
138138

139-
def interruption_adapter
140-
@interruption_adapter ||= JobIteration::Integrations.load(self.class.queue_adapter_name)
141-
end
142-
143139
def enumerator_builder
144140
JobIteration.enumerator_builder.new(self)
145141
end
@@ -299,7 +295,7 @@ def job_should_exit?
299295
return true
300296
end
301297

302-
interruption_adapter.call || (defined?(super) && super)
298+
JobIteration.interruption_adapter.call || (defined?(super) && super)
303299
end
304300

305301
def handle_completed(completed)

lib/job-iteration/test_helper.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def call
2323
# MyJob.perform_now
2424
# end
2525
def iterate_exact_times(n_times)
26-
JobIteration::Integrations.stubs(:load).returns(StoppingSupervisor.new(n_times.size))
26+
JobIteration.stubs(:interruption_adapter).returns(StoppingSupervisor.new(n_times.size))
2727
end
2828

2929
# Stubs interruption adapter to interrupt the job after every sing iteration.
@@ -47,7 +47,7 @@ def mark_job_worker_as_interrupted
4747
def stub_shutdown_adapter_to_return(value)
4848
adapter = mock
4949
adapter.stubs(:call).returns(value)
50-
JobIteration::Integrations.stubs(:load).returns(adapter)
50+
JobIteration.stubs(:interruption_adapter).returns(adapter)
5151
end
5252
end
5353
end

0 commit comments

Comments
 (0)