canvas_sync 0.21.1.beta1 → 0.22.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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