job-iteration 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdace86b05a5a1d98777e2310d85d75679073ca63b5e52b0e83bd000891198cf
4
- data.tar.gz: 4ed5b475931e24bf56d2b8b126bd9354064c438d0ea16f67c10276fb92ccd97b
3
+ metadata.gz: 55bbe81f1fe209219d6dbb5aa469f027834d6b09dbdd6485b305fb4e3aa9bcb1
4
+ data.tar.gz: cf611985f1b8b6dc2cb1d00026fb53d6716ff353d0e59a96235f0e8538e1878b
5
5
  SHA512:
6
- metadata.gz: cf9d7a54a8881146a4a0a1750e99dddf8d32c9db3339411fc61c8477a89f5766e9bb99f493257e4484d56e18ef443cb6a2a0300958342774d2e7ebd2b3e23c32
7
- data.tar.gz: 966c39a5e89ae26343e07000111ba582724b85b4bf9b7a17f790a10b679e297dca94105d3fb42830f987498fb8982037c80cf3e8130b8bede845f9c7532ade41
6
+ metadata.gz: e8857b87a8a999ec88b584896ff7c384310badbf7c2e7c553b9b64fed108cb7756fdc365a0008766d9179c40279ed221c96eca0b363fd5283f6c7d3d676ab82f
7
+ data.tar.gz: 89e9e6d584fef095c6c04b96a369abbf710283dffe9856091eef3fc7492222bd7591f22a8ebaaa7ea76f5d3281cd2c8163ba57d37ead43b095fb08fb377aea3a
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.0
1
+ 3.3.3
data/CHANGELOG.md CHANGED
@@ -1,6 +1,17 @@
1
1
  ### Main (unreleased)
2
+
2
3
  Nil
3
4
 
5
+ ## v1.6.0 (Sep 24, 2024)
6
+
7
+ ### Features
8
+
9
+ - [464](https://github.com/Shopify/job-iteration/pull/464) - Add interruption adapter for [GoodJob](https://github.com/bensheldon/good_job).
10
+ - [505](https://github.com/Shopify/job-iteration/pull/505) - Add interruption adapter for [Solid Queue](https://github.com/rails/solid_queue).
11
+
12
+ ## v1.5.1 (May 29,2024)
13
+ - [483](https://github.com/Shopify/job-iteration/pull/483) - Reverts [#456 Use Arel instead of String for AR Enumerator conditionals](https://github.com/Shopify/job-iteration/pull/456)
14
+
4
15
  ## v1.5.0 (May 29, 2024)
5
16
  ### Changes
6
17
 
data/Gemfile.lock CHANGED
@@ -1,73 +1,92 @@
1
1
  GIT
2
2
  remote: https://github.com/brianmario/mysql2
3
- revision: 43ea8af635f5e23f054294ef7759320d47f30e5f
3
+ revision: f6a9b68b42a51d1a370403f11eb88527dcb42dc6
4
4
  specs:
5
5
  mysql2 (0.5.6)
6
+ bigdecimal
6
7
 
7
8
  PATH
8
9
  remote: .
9
10
  specs:
10
- job-iteration (1.5.0)
11
+ job-iteration (1.6.0)
11
12
  activejob (>= 5.2)
12
13
 
13
14
  GEM
14
15
  remote: https://rubygems.org/
15
16
  specs:
16
- activejob (7.0.7)
17
- activesupport (= 7.0.7)
17
+ activejob (7.1.3.4)
18
+ activesupport (= 7.1.3.4)
18
19
  globalid (>= 0.3.6)
19
- activemodel (7.0.7)
20
- activesupport (= 7.0.7)
21
- activerecord (7.0.7)
22
- activemodel (= 7.0.7)
23
- activesupport (= 7.0.7)
24
- activesupport (7.0.7)
20
+ activemodel (7.1.3.4)
21
+ activesupport (= 7.1.3.4)
22
+ activerecord (7.1.3.4)
23
+ activemodel (= 7.1.3.4)
24
+ activesupport (= 7.1.3.4)
25
+ timeout (>= 0.4.0)
26
+ activesupport (7.1.3.4)
27
+ base64
28
+ bigdecimal
25
29
  concurrent-ruby (~> 1.0, >= 1.0.2)
30
+ connection_pool (>= 2.2.5)
31
+ drb
26
32
  i18n (>= 1.6, < 2)
27
33
  minitest (>= 5.1)
34
+ mutex_m
28
35
  tzinfo (~> 2.0)
29
36
  ast (2.4.2)
37
+ base64 (0.2.0)
38
+ bigdecimal (3.1.8)
30
39
  coderay (1.1.3)
31
- concurrent-ruby (1.2.3)
40
+ concurrent-ruby (1.3.4)
32
41
  connection_pool (2.4.1)
33
42
  csv (3.3.0)
34
- globalid (1.1.0)
35
- activesupport (>= 5.0)
36
- i18n (1.14.4)
43
+ drb (2.2.1)
44
+ globalid (1.2.1)
45
+ activesupport (>= 6.1)
46
+ i18n (1.14.6)
37
47
  concurrent-ruby (~> 1.0)
38
- json (2.7.1)
48
+ json (2.7.2)
39
49
  language_server-protocol (3.17.0.3)
40
- method_source (1.0.0)
41
- minitest (5.19.0)
42
- mocha (2.2.0)
50
+ method_source (1.1.0)
51
+ minitest (5.24.0)
52
+ mocha (2.4.5)
43
53
  ruby2_keywords (>= 0.0.5)
44
54
  mono_logger (1.1.2)
45
55
  multi_json (1.15.0)
46
- parallel (1.24.0)
47
- parser (3.3.0.5)
56
+ mustermann (3.0.0)
57
+ ruby2_keywords (~> 0.0.1)
58
+ mutex_m (0.2.0)
59
+ parallel (1.25.1)
60
+ parser (3.3.3.0)
48
61
  ast (~> 2.4.1)
49
62
  racc
50
63
  pry (0.14.2)
51
64
  coderay (~> 1.1)
52
65
  method_source (~> 1.0)
53
- racc (1.7.3)
54
- rack (3.0.9.1)
66
+ racc (1.8.0)
67
+ rack (3.1.5)
68
+ rack-protection (4.0.0)
69
+ base64 (>= 0.1.0)
70
+ rack (>= 3.0.0, < 4)
71
+ rack-session (2.0.0)
72
+ rack (>= 3.0.0)
55
73
  rainbow (3.1.1)
56
74
  rake (13.2.1)
57
- redis (5.2.0)
75
+ redis (5.3.0)
58
76
  redis-client (>= 0.22.0)
59
- redis-client (0.22.1)
77
+ redis-client (0.22.2)
60
78
  connection_pool
61
79
  redis-namespace (1.11.0)
62
80
  redis (>= 4)
63
- regexp_parser (2.9.0)
81
+ regexp_parser (2.9.2)
64
82
  resque (2.6.0)
65
83
  mono_logger (~> 1.0)
66
84
  multi_json (~> 1.0)
67
85
  redis-namespace (~> 1.6)
68
86
  sinatra (>= 0.9.2)
69
- rexml (3.2.6)
70
- rubocop (1.62.1)
87
+ rexml (3.3.6)
88
+ strscan
89
+ rubocop (1.64.1)
71
90
  json (~> 2.3)
72
91
  language_server-protocol (>= 3.17.0)
73
92
  parallel (~> 1.10)
@@ -78,24 +97,31 @@ GEM
78
97
  rubocop-ast (>= 1.31.1, < 2.0)
79
98
  ruby-progressbar (~> 1.7)
80
99
  unicode-display_width (>= 2.4.0, < 3.0)
81
- rubocop-ast (1.31.2)
82
- parser (>= 3.3.0.4)
83
- rubocop-shopify (2.14.0)
100
+ rubocop-ast (1.31.3)
101
+ parser (>= 3.3.1.0)
102
+ rubocop-shopify (2.15.1)
84
103
  rubocop (~> 1.51)
85
104
  ruby-progressbar (1.13.0)
86
105
  ruby2_keywords (0.0.5)
87
- sidekiq (7.1.2)
106
+ sidekiq (7.2.4)
88
107
  concurrent-ruby (< 2)
89
108
  connection_pool (>= 2.3.0)
90
109
  rack (>= 2.2.4)
91
- redis-client (>= 0.14.0)
92
- sinatra (1.0)
93
- rack (>= 1.0)
94
- sorbet-runtime (0.5.10978)
110
+ redis-client (>= 0.19.0)
111
+ sinatra (4.0.0)
112
+ mustermann (~> 3.0)
113
+ rack (>= 3.0.0, < 4)
114
+ rack-protection (= 4.0.0)
115
+ rack-session (>= 2.0.0, < 3)
116
+ tilt (~> 2.0)
117
+ sorbet-runtime (0.5.11460)
118
+ strscan (3.1.0)
119
+ tilt (2.4.0)
120
+ timeout (0.4.1)
95
121
  tzinfo (2.0.6)
96
122
  concurrent-ruby (~> 1.0)
97
123
  unicode-display_width (2.5.0)
98
- yard (0.9.36)
124
+ yard (0.9.37)
99
125
 
100
126
  PLATFORMS
101
127
  ruby
@@ -118,4 +144,4 @@ DEPENDENCIES
118
144
  yard
119
145
 
120
146
  BUNDLED WITH
121
- 2.5.7
147
+ 2.5.14
@@ -29,6 +29,10 @@ In other words, if you are trying to process 100 records but the job consistentl
29
29
 
30
30
  If no retries are configured or retries are exhausted, Active Job 'bubbles up' the exception to the job backend. Retries by the backend (e.g. Sidekiq) are not supported, meaning that jobs retried by the job backend instead of Active Job will restart from the beginning.
31
31
 
32
+ ## Stopping a job
33
+
34
+ Because jobs typically retry when exceptions are thrown, there is a special mechanism to fully stop a job that still has iterations remaining. To do this, you can `throw(:abort)`. This is then caught by job-iteration and signals that the job should complete now, regardless of its iteration state.
35
+
32
36
  ## Signals
33
37
 
34
38
  It's critical to know [UNIX signals](https://www.tutorialspoint.com/unix/unix-signals-traps.htm) in order to understand how interruption works. There are two main signals that Sidekiq and Resque use: `SIGTERM` and `SIGKILL`. `SIGTERM` is the graceful termination signal which means that the process should exit _soon_, not immediately. For Iteration, it means that we have time to wait for the last iteration to finish and to push job back to the queue with the last cursor position.
@@ -18,8 +18,12 @@ module JobIteration
18
18
  end
19
19
  end
20
20
 
21
- def initialize(relation, columns, position = nil)
22
- @columns = columns
21
+ def initialize(relation, columns = nil, position = nil)
22
+ @columns = if columns
23
+ Array(columns)
24
+ else
25
+ Array(relation.primary_key).map { |pk| "#{relation.table_name}.#{pk}" }
26
+ end
23
27
  self.position = Array.wrap(position)
24
28
  raise ArgumentError, "Must specify at least one column" if columns.empty?
25
29
  if relation.joins_values.present? && !@columns.all? { |column| column.to_s.include?(".") }
@@ -30,7 +34,7 @@ module JobIteration
30
34
  raise ConditionNotSupportedError
31
35
  end
32
36
 
33
- @base_relation = relation.reorder(*@columns)
37
+ @base_relation = relation.reorder(@columns.join(","))
34
38
  @reached_end = false
35
39
  end
36
40
 
@@ -50,10 +54,12 @@ module JobIteration
50
54
 
51
55
  def update_from_record(record)
52
56
  self.position = @columns.map do |column|
53
- if ActiveRecord.version >= Gem::Version.new("7.1.0.alpha") && column.name == "id"
57
+ method = column.to_s.split(".").last
58
+
59
+ if ActiveRecord.version >= Gem::Version.new("7.1.0.alpha") && method == "id"
54
60
  record.id_value
55
61
  else
56
- record.send(column.name)
62
+ record.send(method.to_sym)
57
63
  end
58
64
  end
59
65
  end
@@ -83,14 +89,14 @@ module JobIteration
83
89
  i = @position.size - 1
84
90
  column = @columns[i]
85
91
  conditions = if @columns.size == @position.size
86
- column.gt(@position[i])
92
+ "#{column} > ?"
87
93
  else
88
- column.gteq(@position[i])
94
+ "#{column} >= ?"
89
95
  end
90
96
  while i > 0
91
97
  i -= 1
92
98
  column = @columns[i]
93
- conditions = column.gt(@position[i]).or(column.eq(@position[i]).and(conditions))
99
+ conditions = "#{column} > ? OR (#{column} = ? AND (#{conditions}))"
94
100
  end
95
101
  ret = @position.reduce([conditions]) { |params, value| params << value << value }
96
102
  ret.pop
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./active_record_cursor"
3
+ require_relative "active_record_cursor"
4
4
  module JobIteration
5
5
  # Builds Enumerator based on ActiveRecord Relation. Supports enumerating on rows and batches.
6
6
  # @see EnumeratorBuilder
@@ -11,9 +11,9 @@ module JobIteration
11
11
  @relation = relation
12
12
  @batch_size = batch_size
13
13
  @columns = if columns
14
- Array(columns).map { |col| relation.arel_table[col.to_sym] }
14
+ Array(columns)
15
15
  else
16
- Array(relation.primary_key).map { |pk| relation.arel_table[pk.to_sym] }
16
+ Array(relation.primary_key).map { |pk| "#{relation.table_name}.#{pk}" }
17
17
  end
18
18
  @cursor = cursor
19
19
  end
@@ -45,7 +45,7 @@ module JobIteration
45
45
 
46
46
  def cursor_value(record)
47
47
  positions = @columns.map do |column|
48
- attribute_name = column.name.to_sym
48
+ attribute_name = column.to_s.split(".").last
49
49
  column_value(record, attribute_name)
50
50
  end
51
51
  return positions.first if positions.size == 1
@@ -58,8 +58,8 @@ module JobIteration
58
58
  end
59
59
 
60
60
  def column_value(record, attribute)
61
- value = record.read_attribute(attribute)
62
- case record.class.columns_hash.fetch(attribute.to_s).type
61
+ value = record.read_attribute(attribute.to_sym)
62
+ case record.class.columns_hash.fetch(attribute).type
63
63
  when :datetime
64
64
  value.strftime(SQL_DATETIME_WITH_NSEC)
65
65
  else
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./active_record_batch_enumerator"
4
- require_relative "./active_record_enumerator"
5
- require_relative "./csv_enumerator"
6
- require_relative "./throttle_enumerator"
7
- require_relative "./nested_enumerator"
3
+ require_relative "active_record_batch_enumerator"
4
+ require_relative "active_record_enumerator"
5
+ require_relative "csv_enumerator"
6
+ require_relative "throttle_enumerator"
7
+ require_relative "nested_enumerator"
8
8
  require "forwardable"
9
9
 
10
10
  module JobIteration
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "good_job"
5
+ rescue LoadError
6
+ # GoodJob is not available, no need to load the adapter
7
+ return
8
+ end
9
+
10
+ begin
11
+ # GoodJob.current_thread_shutting_down? was introduced in GoodJob 3.26
12
+ gem("good_job", ">= 3.26")
13
+ rescue Gem::LoadError
14
+ warn("job-iteration's interruption adapter for GoodJob requires GoodJob 3.26 or newer")
15
+ return
16
+ end
17
+
18
+ module JobIteration
19
+ module InterruptionAdapters
20
+ module GoodJobAdapter
21
+ class << self
22
+ def call
23
+ !!::GoodJob.current_thread_shutting_down?
24
+ end
25
+ end
26
+ end
27
+
28
+ register(:good_job, GoodJobAdapter)
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "solid_queue"
5
+ rescue LoadError
6
+ # SolidQueue is not available, no need to load the adapter
7
+ return
8
+ end
9
+
10
+ begin
11
+ # SolidQueue.on_worker_stop was introduced in SolidQueue 0.7.1
12
+ gem("solid_queue", ">= 0.7.1")
13
+ rescue Gem::LoadError
14
+ warn("job-iteration's interruption adapter for SolidQueue requires SolidQueue 0.7.1 or newer")
15
+ return
16
+ end
17
+
18
+ module JobIteration
19
+ module InterruptionAdapters
20
+ module SolidQueueAdapter
21
+ class << self
22
+ attr_accessor :stopping
23
+
24
+ def call
25
+ stopping
26
+ end
27
+ end
28
+
29
+ SolidQueue.on_worker_stop do
30
+ SolidQueueAdapter.stopping = true
31
+ end
32
+ end
33
+
34
+ register(:solid_queue, SolidQueueAdapter)
35
+ end
36
+ end
@@ -4,7 +4,7 @@ require_relative "interruption_adapters/null_adapter"
4
4
 
5
5
  module JobIteration
6
6
  module InterruptionAdapters
7
- BUNDLED_ADAPTERS = [:resque, :sidekiq].freeze # @api private
7
+ BUNDLED_ADAPTERS = [:good_job, :resque, :sidekiq, :solid_queue].freeze # @api private
8
8
 
9
9
  class << self
10
10
  # Returns adapter for specified name.
@@ -14,7 +14,7 @@ module JobIteration
14
14
  # The time when the job starts running. If the job is interrupted and runs again, the value is updated.
15
15
  attr_accessor :start_time
16
16
 
17
- # The total time the job has been running, including multiple iterations.
17
+ # The total time (in seconds) the job has been running, including multiple iterations.
18
18
  # The time isn't reset if the job is interrupted.
19
19
  attr_accessor :total_time
20
20
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JobIteration
4
- VERSION = "1.5.0"
4
+ VERSION = "1.6.0"
5
5
  end
data/lib/job-iteration.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_job"
4
- require_relative "./job-iteration/version"
5
- require_relative "./job-iteration/enumerator_builder"
6
- require_relative "./job-iteration/interruption_adapters"
7
- require_relative "./job-iteration/iteration"
8
- require_relative "./job-iteration/log_subscriber"
9
- require_relative "./job-iteration/railtie"
4
+ require_relative "job-iteration/version"
5
+ require_relative "job-iteration/enumerator_builder"
6
+ require_relative "job-iteration/interruption_adapters"
7
+ require_relative "job-iteration/iteration"
8
+ require_relative "job-iteration/log_subscriber"
9
+ require_relative "job-iteration/railtie"
10
10
 
11
11
  module JobIteration
12
12
  Deprecation = ActiveSupport::Deprecation.new("2.0", "JobIteration")
@@ -62,9 +62,9 @@ module JobIteration
62
62
  # Overrides interruption checks based on queue adapter.
63
63
  # @deprecated - Use JobIteration::InterruptionAdapters.register(:foo, callable) instead.
64
64
  def interruption_adapter=(adapter)
65
- Deprecation.warn("Setting JobIteration.interruption_adapter is deprecated. "\
66
- "Use JobIteration::InterruptionAdapters.register(:foo, callable) instead "\
67
- "to register the callable (a proc, method, or other object responding to #call) "\
65
+ Deprecation.warn("Setting JobIteration.interruption_adapter is deprecated. " \
66
+ "Use JobIteration::InterruptionAdapters.register(:foo, callable) instead " \
67
+ "to register the callable (a proc, method, or other object responding to #call) " \
68
68
  "as the interruption adapter for queue adapter :foo.")
69
69
  @interruption_adapter = adapter
70
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: job-iteration
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-29 00:00:00.000000000 Z
11
+ date: 2024-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -54,7 +54,6 @@ files:
54
54
  - guides/custom-enumerator.md
55
55
  - guides/iteration-how-it-works.md
56
56
  - guides/throttling.md
57
- - isogun.yml
58
57
  - job-iteration.gemspec
59
58
  - lib/job-iteration.rb
60
59
  - lib/job-iteration/active_record_batch_enumerator.rb
@@ -63,9 +62,11 @@ files:
63
62
  - lib/job-iteration/csv_enumerator.rb
64
63
  - lib/job-iteration/enumerator_builder.rb
65
64
  - lib/job-iteration/interruption_adapters.rb
65
+ - lib/job-iteration/interruption_adapters/good_job_adapter.rb
66
66
  - lib/job-iteration/interruption_adapters/null_adapter.rb
67
67
  - lib/job-iteration/interruption_adapters/resque_adapter.rb
68
68
  - lib/job-iteration/interruption_adapters/sidekiq_adapter.rb
69
+ - lib/job-iteration/interruption_adapters/solid_queue_adapter.rb
69
70
  - lib/job-iteration/iteration.rb
70
71
  - lib/job-iteration/log_subscriber.rb
71
72
  - lib/job-iteration/nested_enumerator.rb
@@ -79,7 +80,7 @@ licenses:
79
80
  metadata:
80
81
  changelog_uri: https://github.com/Shopify/job-iteration/blob/main/CHANGELOG.md
81
82
  allowed_push_host: https://rubygems.org
82
- post_install_message:
83
+ post_install_message:
83
84
  rdoc_options: []
84
85
  require_paths:
85
86
  - lib
@@ -94,8 +95,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
95
  - !ruby/object:Gem::Version
95
96
  version: '0'
96
97
  requirements: []
97
- rubygems_version: 3.5.10
98
- signing_key:
98
+ rubygems_version: 3.5.18
99
+ signing_key:
99
100
  specification_version: 4
100
101
  summary: Makes your background jobs interruptible and resumable.
101
102
  test_files: []
data/isogun.yml DELETED
@@ -1,11 +0,0 @@
1
- name: job-iteration
2
-
3
- vm:
4
- ip_address: 192.168.64.142
5
- memory: 1G
6
- cores: 2
7
-
8
- services:
9
- - redis
10
- - mysql
11
-