canvas_sync 0.21.1 → 0.22.0.beta1

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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/lib/canvas_sync/concerns/auto_relations.rb +11 -0
  3. data/lib/canvas_sync/config.rb +3 -5
  4. data/lib/canvas_sync/generators/templates/models/rubric.rb +2 -1
  5. data/lib/canvas_sync/job_batches/batch.rb +432 -402
  6. data/lib/canvas_sync/job_batches/callback.rb +100 -114
  7. data/lib/canvas_sync/job_batches/chain_builder.rb +194 -196
  8. data/lib/canvas_sync/job_batches/{active_job.rb → compat/active_job.rb} +2 -2
  9. data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/helpers.rb +1 -1
  10. data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web.rb +3 -3
  11. data/lib/canvas_sync/job_batches/{sidekiq.rb → compat/sidekiq.rb} +35 -22
  12. data/lib/canvas_sync/job_batches/compat.rb +20 -0
  13. data/lib/canvas_sync/job_batches/context_hash.rb +124 -126
  14. data/lib/canvas_sync/job_batches/jobs/base_job.rb +2 -4
  15. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +14 -16
  16. data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +125 -127
  17. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +14 -16
  18. data/lib/canvas_sync/job_batches/pool.rb +193 -195
  19. data/lib/canvas_sync/job_batches/redis_model.rb +50 -52
  20. data/lib/canvas_sync/job_batches/redis_script.rb +129 -131
  21. data/lib/canvas_sync/job_batches/status.rb +85 -87
  22. data/lib/canvas_sync/job_uniqueness/compat/active_job.rb +75 -0
  23. data/lib/canvas_sync/job_uniqueness/compat/sidekiq.rb +135 -0
  24. data/lib/canvas_sync/job_uniqueness/compat.rb +20 -0
  25. data/lib/canvas_sync/job_uniqueness/configuration.rb +25 -0
  26. data/lib/canvas_sync/job_uniqueness/job_uniqueness.rb +47 -0
  27. data/lib/canvas_sync/job_uniqueness/lock_context.rb +171 -0
  28. data/lib/canvas_sync/job_uniqueness/locksmith.rb +92 -0
  29. data/lib/canvas_sync/job_uniqueness/on_conflict/base.rb +32 -0
  30. data/lib/canvas_sync/job_uniqueness/on_conflict/log.rb +13 -0
  31. data/lib/canvas_sync/job_uniqueness/on_conflict/null_strategy.rb +9 -0
  32. data/lib/canvas_sync/job_uniqueness/on_conflict/raise.rb +11 -0
  33. data/lib/canvas_sync/job_uniqueness/on_conflict/reject.rb +21 -0
  34. data/lib/canvas_sync/job_uniqueness/on_conflict/reschedule.rb +20 -0
  35. data/lib/canvas_sync/job_uniqueness/on_conflict.rb +41 -0
  36. data/lib/canvas_sync/job_uniqueness/strategy/base.rb +104 -0
  37. data/lib/canvas_sync/job_uniqueness/strategy/until_and_while_executing.rb +35 -0
  38. data/lib/canvas_sync/job_uniqueness/strategy/until_executed.rb +20 -0
  39. data/lib/canvas_sync/job_uniqueness/strategy/until_executing.rb +20 -0
  40. data/lib/canvas_sync/job_uniqueness/strategy/until_expired.rb +16 -0
  41. data/lib/canvas_sync/job_uniqueness/strategy/while_executing.rb +26 -0
  42. data/lib/canvas_sync/job_uniqueness/strategy.rb +27 -0
  43. data/lib/canvas_sync/job_uniqueness/unique_job_common.rb +79 -0
  44. data/lib/canvas_sync/misc_helper.rb +1 -1
  45. data/lib/canvas_sync/version.rb +1 -1
  46. data/lib/canvas_sync.rb +4 -3
  47. data/spec/dummy/app/models/rubric.rb +2 -1
  48. data/spec/dummy/config/environments/test.rb +1 -1
  49. data/spec/job_batching/batch_spec.rb +49 -7
  50. data/spec/job_batching/{active_job_spec.rb → compat/active_job_spec.rb} +2 -2
  51. data/spec/job_batching/{sidekiq_spec.rb → compat/sidekiq_spec.rb} +14 -12
  52. data/spec/job_batching/flow_spec.rb +1 -1
  53. data/spec/job_batching/integration_helper.rb +1 -1
  54. data/spec/job_batching/status_spec.rb +2 -2
  55. data/spec/job_uniqueness/compat/active_job_spec.rb +49 -0
  56. data/spec/job_uniqueness/compat/sidekiq_spec.rb +68 -0
  57. data/spec/job_uniqueness/lock_context_spec.rb +95 -0
  58. data/spec/job_uniqueness/on_conflict/log_spec.rb +11 -0
  59. data/spec/job_uniqueness/on_conflict/raise_spec.rb +10 -0
  60. data/spec/job_uniqueness/on_conflict/reschedule_spec.rb +24 -0
  61. data/spec/job_uniqueness/on_conflict_spec.rb +16 -0
  62. data/spec/job_uniqueness/spec_helper.rb +14 -0
  63. data/spec/job_uniqueness/strategy/base_spec.rb +100 -0
  64. data/spec/job_uniqueness/strategy/until_and_while_executing_spec.rb +48 -0
  65. data/spec/job_uniqueness/strategy/until_executed_spec.rb +23 -0
  66. data/spec/job_uniqueness/strategy/until_executing_spec.rb +23 -0
  67. data/spec/job_uniqueness/strategy/until_expired_spec.rb +23 -0
  68. data/spec/job_uniqueness/strategy/while_executing_spec.rb +33 -0
  69. data/spec/job_uniqueness/support/lock_strategy.rb +28 -0
  70. data/spec/job_uniqueness/support/on_conflict.rb +24 -0
  71. data/spec/job_uniqueness/support/test_worker.rb +19 -0
  72. data/spec/job_uniqueness/unique_job_common_spec.rb +45 -0
  73. data/spec/spec_helper.rb +1 -1
  74. metadata +278 -204
  75. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/batches_assets/css/styles.less +0 -0
  76. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/batches_assets/js/batch_tree.js +0 -0
  77. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/batches_assets/js/util.js +0 -0
  78. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_batch_tree.erb +0 -0
  79. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_batches_table.erb +0 -0
  80. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_common.erb +0 -0
  81. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_jobs_table.erb +0 -0
  82. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_pagination.erb +0 -0
  83. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/batch.erb +0 -0
  84. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/batches.erb +0 -0
  85. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/pool.erb +0 -0
  86. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/pools.erb +0 -0
@@ -4,159 +4,157 @@ require 'erb'
4
4
 
5
5
  # Modified from https://github.com/Shopify/wolverine/blob/master/lib/wolverine/script.rb
6
6
 
7
- module CanvasSync
8
- module JobBatches
9
- # {RedisScript} represents a lua script in the filesystem. It loads the script
10
- # from disk and handles talking to redis to execute it. Error handling
11
- # is handled by {LuaError}.
12
- class RedisScript
13
-
14
- # Loads the script file from disk and calculates its +SHA1+ sum.
15
- #
16
- # @param file [Pathname] the full path to the indicated file
17
- def initialize(file)
18
- @file = Pathname.new(file)
19
- end
7
+ module CanvasSync::JobBatches
8
+ # {RedisScript} represents a lua script in the filesystem. It loads the script
9
+ # from disk and handles talking to redis to execute it. Error handling
10
+ # is handled by {LuaError}.
11
+ class RedisScript
12
+
13
+ # Loads the script file from disk and calculates its +SHA1+ sum.
14
+ #
15
+ # @param file [Pathname] the full path to the indicated file
16
+ def initialize(file)
17
+ @file = Pathname.new(file)
18
+ end
20
19
 
21
- # Passes the script and supplied arguments to redis for evaulation.
22
- # It first attempts to use a script redis has already cached by using
23
- # the +EVALSHA+ command, but falls back to providing the full script
24
- # text via +EVAL+ if redis has not seen this script before. Future
25
- # invocations will then use +EVALSHA+ without erroring.
26
- #
27
- # @param redis [Redis] the redis connection to run against
28
- # @param args [*Objects] the arguments to the script
29
- # @return [Object] the value passed back by redis after script execution
30
- # @raise [LuaError] if the script failed to compile of encountered a
31
- # runtime error
32
- def call(redis, *args)
33
- t = Time.now
34
- begin
35
- redis.evalsha(digest, *args)
36
- rescue => e
37
- e.message =~ /NOSCRIPT/ ? redis.eval(content, *args) : raise
38
- end
20
+ # Passes the script and supplied arguments to redis for evaulation.
21
+ # It first attempts to use a script redis has already cached by using
22
+ # the +EVALSHA+ command, but falls back to providing the full script
23
+ # text via +EVAL+ if redis has not seen this script before. Future
24
+ # invocations will then use +EVALSHA+ without erroring.
25
+ #
26
+ # @param redis [Redis] the redis connection to run against
27
+ # @param args [*Objects] the arguments to the script
28
+ # @return [Object] the value passed back by redis after script execution
29
+ # @raise [LuaError] if the script failed to compile of encountered a
30
+ # runtime error
31
+ def call(redis, *args)
32
+ t = Time.now
33
+ begin
34
+ redis.evalsha(digest, *args)
39
35
  rescue => e
40
- if LuaError.intercepts?(e)
41
- raise LuaError.new(e, @file, content)
42
- else
43
- raise
44
- end
36
+ e.message =~ /NOSCRIPT/ ? redis.eval(content, *args) : raise
45
37
  end
46
-
47
- def content
48
- @content ||= load_lua(@file)
38
+ rescue => e
39
+ if LuaError.intercepts?(e)
40
+ raise LuaError.new(e, @file, content)
41
+ else
42
+ raise
49
43
  end
44
+ end
50
45
 
51
- def digest
52
- @digest ||= Digest::SHA1.hexdigest content
53
- end
46
+ def content
47
+ @content ||= load_lua(@file)
48
+ end
54
49
 
55
- private
50
+ def digest
51
+ @digest ||= Digest::SHA1.hexdigest content
52
+ end
56
53
 
57
- def script_path
58
- Rails.root + 'app/redis_lua'
59
- end
54
+ private
60
55
 
61
- def relative_path
62
- @path ||= @file.relative_path_from(script_path)
63
- end
56
+ def script_path
57
+ Rails.root + 'app/redis_lua'
58
+ end
64
59
 
65
- def load_lua(file)
66
- TemplateContext.new(script_path).template(script_path + file)
67
- end
60
+ def relative_path
61
+ @path ||= @file.relative_path_from(script_path)
62
+ end
68
63
 
69
- class TemplateContext
70
- def initialize(script_path)
71
- @script_path = script_path
72
- end
64
+ def load_lua(file)
65
+ TemplateContext.new(script_path).template(script_path + file)
66
+ end
73
67
 
74
- def template(pathname)
75
- @partial_templates ||= {}
76
- ERB.new(File.read(pathname)).result binding
77
- end
68
+ class TemplateContext
69
+ def initialize(script_path)
70
+ @script_path = script_path
71
+ end
78
72
 
79
- # helper method to include a lua partial within another lua script
80
- #
81
- # @param relative_path [String] the relative path to the script from
82
- # `script_path`
83
- def include_partial(relative_path)
84
- unless @partial_templates.has_key? relative_path
85
- @partial_templates[relative_path] = nil
86
- template( Pathname.new("#{@script_path}/#{relative_path}") )
87
- end
88
- end
73
+ def template(pathname)
74
+ @partial_templates ||= {}
75
+ ERB.new(File.read(pathname)).result binding
89
76
  end
90
77
 
91
- # Reformats errors raised by redis representing failures while executing
92
- # a lua script. The default errors have confusing messages and backtraces,
93
- # and a type of +RuntimeError+. This class improves the message and
94
- # modifies the backtrace to include the lua script itself in a reasonable
95
- # way.
96
- class LuaError < StandardError
97
- PATTERN = /ERR Error (compiling|running) script \(.*?\): .*?:(\d+): (.*)/
98
- WOLVERINE_LIB_PATH = File.expand_path('../../', __FILE__)
99
- CONTEXT_LINE_NUMBER = 2
100
-
101
- attr_reader :error, :file, :content
102
-
103
- # Is this error one that should be reformatted?
104
- #
105
- # @param error [StandardError] the original error raised by redis
106
- # @return [Boolean] is this an error that should be reformatted?
107
- def self.intercepts? error
108
- error.message =~ PATTERN
78
+ # helper method to include a lua partial within another lua script
79
+ #
80
+ # @param relative_path [String] the relative path to the script from
81
+ # `script_path`
82
+ def include_partial(relative_path)
83
+ unless @partial_templates.has_key? relative_path
84
+ @partial_templates[relative_path] = nil
85
+ template( Pathname.new("#{@script_path}/#{relative_path}") )
109
86
  end
87
+ end
88
+ end
110
89
 
111
- # Initialize a new {LuaError} from an existing redis error, adjusting
112
- # the message and backtrace in the process.
113
- #
114
- # @param error [StandardError] the original error raised by redis
115
- # @param file [Pathname] full path to the lua file the error ocurred in
116
- # @param content [String] lua file content the error ocurred in
117
- def initialize error, file, content
118
- @error = error
119
- @file = file
120
- @content = content
121
-
122
- @error.message =~ PATTERN
123
- _stage, line_number, message = $1, $2, $3
124
- error_context = generate_error_context(content, line_number.to_i)
125
-
126
- super "#{message}\n\n#{error_context}\n\n"
127
- set_backtrace generate_backtrace file, line_number
128
- end
90
+ # Reformats errors raised by redis representing failures while executing
91
+ # a lua script. The default errors have confusing messages and backtraces,
92
+ # and a type of +RuntimeError+. This class improves the message and
93
+ # modifies the backtrace to include the lua script itself in a reasonable
94
+ # way.
95
+ class LuaError < StandardError
96
+ PATTERN = /ERR Error (compiling|running) script \(.*?\): .*?:(\d+): (.*)/
97
+ WOLVERINE_LIB_PATH = File.expand_path('../../', __FILE__)
98
+ CONTEXT_LINE_NUMBER = 2
129
99
 
130
- private
100
+ attr_reader :error, :file, :content
131
101
 
132
- def generate_error_context(content, line_number)
133
- lines = content.lines.to_a
134
- beginning_line_number = [1, line_number - CONTEXT_LINE_NUMBER].max
135
- ending_line_number = [lines.count, line_number + CONTEXT_LINE_NUMBER].min
136
- line_number_width = ending_line_number.to_s.length
102
+ # Is this error one that should be reformatted?
103
+ #
104
+ # @param error [StandardError] the original error raised by redis
105
+ # @return [Boolean] is this an error that should be reformatted?
106
+ def self.intercepts? error
107
+ error.message =~ PATTERN
108
+ end
137
109
 
138
- (beginning_line_number..ending_line_number).map do |number|
139
- indicator = number == line_number ? '=>' : ' '
140
- formatted_number = "%#{line_number_width}d" % number
141
- " #{indicator} #{formatted_number}: #{lines[number - 1]}"
142
- end.join.chomp
143
- end
110
+ # Initialize a new {LuaError} from an existing redis error, adjusting
111
+ # the message and backtrace in the process.
112
+ #
113
+ # @param error [StandardError] the original error raised by redis
114
+ # @param file [Pathname] full path to the lua file the error ocurred in
115
+ # @param content [String] lua file content the error ocurred in
116
+ def initialize error, file, content
117
+ @error = error
118
+ @file = file
119
+ @content = content
120
+
121
+ @error.message =~ PATTERN
122
+ _stage, line_number, message = $1, $2, $3
123
+ error_context = generate_error_context(content, line_number.to_i)
124
+
125
+ super "#{message}\n\n#{error_context}\n\n"
126
+ set_backtrace generate_backtrace file, line_number
127
+ end
144
128
 
145
- def generate_backtrace(file, line_number)
146
- pre_wolverine = backtrace_before_entering_wolverine(@error.backtrace)
147
- index_of_first_wolverine_line = (@error.backtrace.size - pre_wolverine.size - 1)
148
- pre_wolverine.unshift(@error.backtrace[index_of_first_wolverine_line])
149
- pre_wolverine.unshift("#{file}:#{line_number}")
150
- pre_wolverine
151
- end
129
+ private
152
130
 
153
- def backtrace_before_entering_wolverine(backtrace)
154
- backtrace.reverse.take_while { |line| ! line_from_wolverine(line) }.reverse
155
- end
131
+ def generate_error_context(content, line_number)
132
+ lines = content.lines.to_a
133
+ beginning_line_number = [1, line_number - CONTEXT_LINE_NUMBER].max
134
+ ending_line_number = [lines.count, line_number + CONTEXT_LINE_NUMBER].min
135
+ line_number_width = ending_line_number.to_s.length
136
+
137
+ (beginning_line_number..ending_line_number).map do |number|
138
+ indicator = number == line_number ? '=>' : ' '
139
+ formatted_number = "%#{line_number_width}d" % number
140
+ " #{indicator} #{formatted_number}: #{lines[number - 1]}"
141
+ end.join.chomp
142
+ end
156
143
 
157
- def line_from_wolverine(line)
158
- line.split(':').first.include?(WOLVERINE_LIB_PATH)
159
- end
144
+ def generate_backtrace(file, line_number)
145
+ pre_wolverine = backtrace_before_entering_wolverine(@error.backtrace)
146
+ index_of_first_wolverine_line = (@error.backtrace.size - pre_wolverine.size - 1)
147
+ pre_wolverine.unshift(@error.backtrace[index_of_first_wolverine_line])
148
+ pre_wolverine.unshift("#{file}:#{line_number}")
149
+ pre_wolverine
150
+ end
151
+
152
+ def backtrace_before_entering_wolverine(backtrace)
153
+ backtrace.reverse.take_while { |line| ! line_from_wolverine(line) }.reverse
154
+ end
155
+
156
+ def line_from_wolverine(line)
157
+ line.split(':').first.include?(WOLVERINE_LIB_PATH)
160
158
  end
161
159
  end
162
160
  end
@@ -1,90 +1,88 @@
1
- module CanvasSync
2
- module JobBatches
3
- class Batch
4
- class Status
5
- attr_reader :bid
6
-
7
- def initialize(bid)
8
- bid = bid.bid if bid.is_a?(Batch)
9
- @bid = bid
10
- end
11
-
12
- def join
13
- raise "Not supported"
14
- end
15
-
16
- def pending
17
- Batch.redis { |r| r.hget("BID-#{bid}", 'pending') }.to_i
18
- end
19
-
20
- def failures
21
- Batch.redis { |r| r.scard("BID-#{bid}-failed") }.to_i
22
- end
23
-
24
- def dead
25
- Batch.redis { |r| r.scard("BID-#{bid}-dead") }.to_i
26
- end
27
-
28
- def completed_count
29
- job_count - pending
30
- end
31
-
32
- def job_count
33
- Batch.redis { |r| r.hget("BID-#{bid}", "job_count") }.to_i
34
- end
35
-
36
- def created_at
37
- Batch.redis { |r| r.hget("BID-#{bid}", 'created_at') }
38
- end
39
-
40
- def parent_bid
41
- Batch.redis { |r| r.hget("BID-#{bid}", "parent_bid") }
42
- end
43
-
44
- def failure_info
45
- Batch.redis { |r| r.smembers("BID-#{bid}-failed") } || []
46
- end
47
-
48
- def complete?
49
- 'true' == Batch.redis { |r| r.hget("BID-#{bid}", 'complete') }
50
- end
51
-
52
- def success?
53
- 'true' == Batch.redis { |r| r.hget("BID-#{bid}", 'success') }
54
- end
55
-
56
- def child_count
57
- Batch.redis { |r| r.hget("BID-#{bid}", 'children') }.to_i
58
- end
59
-
60
- def completed_children_count
61
- Batch.redis { |r| r.scard("BID-#{bid}-batches-complete") }.to_i
62
- end
63
-
64
- def successful_children_count
65
- Batch.redis { |r| r.scard("BID-#{bid}-batches-success") }.to_i
66
- end
67
-
68
- def failed_children_count
69
- Batch.redis { |r| r.scard("BID-#{bid}-batches-failed") }.to_i
70
- end
71
-
72
- def data
73
- {
74
- bid: bid,
75
- failures: failures,
76
- pending: pending,
77
- created_at: created_at,
78
- complete: complete?,
79
- success: success?,
80
- failure_info: failure_info,
81
- parent_bid: parent_bid,
82
- child_count: child_count,
83
- completed_children_count: completed_children_count,
84
- successful_children_count: successful_children_count,
85
- failed_children_count: failed_children_count,
86
- }
87
- end
1
+ module CanvasSync::JobBatches
2
+ class Batch
3
+ class Status
4
+ attr_reader :bid
5
+
6
+ def initialize(bid)
7
+ bid = bid.bid if bid.is_a?(Batch)
8
+ @bid = bid
9
+ end
10
+
11
+ def join
12
+ raise "Not supported"
13
+ end
14
+
15
+ def pending
16
+ Batch.redis { |r| r.hget("BID-#{bid}", 'pending') }.to_i
17
+ end
18
+
19
+ def failures
20
+ Batch.redis { |r| r.scard("BID-#{bid}-failed") }.to_i
21
+ end
22
+
23
+ def dead
24
+ Batch.redis { |r| r.scard("BID-#{bid}-dead") }.to_i
25
+ end
26
+
27
+ def completed_count
28
+ job_count - pending
29
+ end
30
+
31
+ def job_count
32
+ Batch.redis { |r| r.hget("BID-#{bid}", "job_count") }.to_i
33
+ end
34
+
35
+ def created_at
36
+ Batch.redis { |r| r.hget("BID-#{bid}", 'created_at') }
37
+ end
38
+
39
+ def parent_bid
40
+ Batch.redis { |r| r.hget("BID-#{bid}", "parent_bid") }
41
+ end
42
+
43
+ def failure_info
44
+ Batch.redis { |r| r.smembers("BID-#{bid}-failed") } || []
45
+ end
46
+
47
+ def complete?
48
+ 'true' == Batch.redis { |r| r.hget("BID-#{bid}", 'complete') }
49
+ end
50
+
51
+ def success?
52
+ 'true' == Batch.redis { |r| r.hget("BID-#{bid}", 'success') }
53
+ end
54
+
55
+ def child_count
56
+ Batch.redis { |r| r.hget("BID-#{bid}", 'children') }.to_i
57
+ end
58
+
59
+ def completed_children_count
60
+ Batch.redis { |r| r.scard("BID-#{bid}-batches-complete") }.to_i
61
+ end
62
+
63
+ def successful_children_count
64
+ Batch.redis { |r| r.scard("BID-#{bid}-batches-success") }.to_i
65
+ end
66
+
67
+ def failed_children_count
68
+ Batch.redis { |r| r.scard("BID-#{bid}-batches-failed") }.to_i
69
+ end
70
+
71
+ def data
72
+ {
73
+ bid: bid,
74
+ failures: failures,
75
+ pending: pending,
76
+ created_at: created_at,
77
+ complete: complete?,
78
+ success: success?,
79
+ failure_info: failure_info,
80
+ parent_bid: parent_bid,
81
+ child_count: child_count,
82
+ completed_children_count: completed_children_count,
83
+ successful_children_count: successful_children_count,
84
+ failed_children_count: failed_children_count,
85
+ }
88
86
  end
89
87
  end
90
88
  end
@@ -0,0 +1,75 @@
1
+
2
+ module CanvasSync::JobUniqueness
3
+ module Compat
4
+ module ActiveJob
5
+
6
+ class ActiveJobLockContext < LockContext
7
+ def job_scheduled_at
8
+ job_instance&.scheduled_at
9
+ end
10
+
11
+ def reenqueue(schedule_in:)
12
+ job_class.set(
13
+ queue: job_queue.to_sym,
14
+ set: schedule_in,
15
+ priortity: job_instance.priority,
16
+ ).perform_later(*job_instance.arguments)
17
+ end
18
+ end
19
+
20
+ module UniqueJobExtension
21
+ extend ActiveSupport::Concern
22
+
23
+ included do
24
+ set_callback(:enqueue, :around, prepend: true) do |job, block|
25
+ ctx = uniqueness_lock_context
26
+ @uniqueness_cache_data = ctx.cache_data
27
+ ctx.handle_lifecycle!(:enqueue, &block)
28
+ end
29
+
30
+ around_perform do |job, block|
31
+ ctx = uniqueness_lock_context
32
+ ctx.handle_lifecycle!(:perform, &block)
33
+ end
34
+ end
35
+
36
+ def serialize
37
+ super.tap do |data|
38
+ data['uniqueness_cache_data'] = @uniqueness_cache_data.stringify_keys
39
+ end
40
+ end
41
+
42
+ def deserialize(data)
43
+ super
44
+ @uniqueness_cache_data = data['uniqueness_cache_data'].symbolize_keys
45
+ end
46
+
47
+ def uniqueness_lock_context
48
+ ActiveJobLockContext.new({
49
+ job_clazz: self.class,
50
+ jid: self.job_id,
51
+ args: self.arguments,
52
+ queue: self.queue_name,
53
+ **(@uniqueness_cache_data || {})
54
+ }, job_instance: self)
55
+ end
56
+ end
57
+
58
+ module JobExtension
59
+ extend ActiveSupport::Concern
60
+ include UniqueJobCommon
61
+
62
+ class_methods do
63
+ def ensure_uniqueness(**kwargs)
64
+ super(**kwargs)
65
+ include UniqueJobExtension
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.configure
71
+ ::ActiveJob::Base.include JobExtension
72
+ end
73
+ end
74
+ end
75
+ end