acidic_job 1.0.0.beta.10 → 1.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +12 -36
- data/.gitignore +0 -5
- data/.ruby_version +1 -0
- data/Gemfile +31 -0
- data/Gemfile.lock +130 -136
- data/README.md +58 -278
- data/acidic_job.gemspec +2 -15
- data/bin/console +2 -4
- data/lib/acidic_job/awaiting.rb +68 -0
- data/lib/acidic_job/errors.rb +19 -11
- data/lib/acidic_job/extensions/action_mailer.rb +11 -3
- data/lib/acidic_job/extensions/active_job.rb +39 -0
- data/lib/acidic_job/extensions/noticed.rb +11 -5
- data/lib/acidic_job/extensions/sidekiq.rb +101 -0
- data/lib/acidic_job/finished_point.rb +5 -3
- data/lib/acidic_job/idempotency_key.rb +15 -18
- data/lib/acidic_job/perform_wrapper.rb +36 -9
- data/lib/acidic_job/recovery_point.rb +3 -2
- data/lib/acidic_job/run.rb +42 -268
- data/lib/acidic_job/staging.rb +30 -0
- data/lib/acidic_job/step.rb +83 -0
- data/lib/acidic_job/version.rb +1 -1
- data/lib/acidic_job.rb +244 -20
- data/lib/generators/acidic_job_generator.rb +35 -0
- data/lib/generators/templates/create_acidic_job_runs_migration.rb.erb +19 -0
- metadata +15 -209
- data/.github/FUNDING.yml +0 -13
- data/.tool-versions +0 -1
- data/UPGRADE_GUIDE.md +0 -81
- data/combustion/log/test.log +0 -0
- data/gemfiles/rails_6.1_sidekiq_6.4.gemfile +0 -10
- data/gemfiles/rails_6.1_sidekiq_6.5.gemfile +0 -10
- data/gemfiles/rails_7.0_sidekiq_6.4.gemfile +0 -10
- data/gemfiles/rails_7.0_sidekiq_6.5.gemfile +0 -10
- data/gemfiles/rails_7.1_sidekiq_6.4.gemfile +0 -10
- data/gemfiles/rails_7.1_sidekiq_6.5.gemfile +0 -10
- data/lib/acidic_job/active_kiq.rb +0 -114
- data/lib/acidic_job/arguments.rb +0 -22
- data/lib/acidic_job/base.rb +0 -11
- data/lib/acidic_job/logger.rb +0 -31
- data/lib/acidic_job/mixin.rb +0 -250
- data/lib/acidic_job/processor.rb +0 -95
- data/lib/acidic_job/rails.rb +0 -40
- data/lib/acidic_job/serializer.rb +0 -24
- data/lib/acidic_job/serializers/exception_serializer.rb +0 -41
- data/lib/acidic_job/serializers/finished_point_serializer.rb +0 -24
- data/lib/acidic_job/serializers/job_serializer.rb +0 -27
- data/lib/acidic_job/serializers/range_serializer.rb +0 -28
- data/lib/acidic_job/serializers/recovery_point_serializer.rb +0 -25
- data/lib/acidic_job/serializers/worker_serializer.rb +0 -27
- data/lib/acidic_job/test_case.rb +0 -9
- data/lib/acidic_job/testing.rb +0 -73
- data/lib/acidic_job/workflow.rb +0 -70
- data/lib/acidic_job/workflow_builder.rb +0 -35
- data/lib/acidic_job/workflow_step.rb +0 -103
- data/lib/generators/acidic_job/drop_tables_generator.rb +0 -26
- data/lib/generators/acidic_job/install_generator.rb +0 -27
- data/lib/generators/acidic_job/templates/create_acidic_job_runs_migration.rb.erb +0 -19
- data/lib/generators/acidic_job/templates/drop_acidic_job_keys_migration.rb.erb +0 -27
data/lib/acidic_job/mixin.rb
DELETED
@@ -1,250 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
|
-
module AcidicJob
|
6
|
-
module Mixin
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
def self.wire_up(other)
|
10
|
-
# Ensure our `perform` method always runs first to gather parameters
|
11
|
-
# and run perform callbacks for Sidekiq workers
|
12
|
-
other.prepend PerformWrapper
|
13
|
-
|
14
|
-
# By default, we unique job runs by the `job_id`
|
15
|
-
other.instance_variable_set(:@acidic_identifier, :job_id)
|
16
|
-
# However, you can customize this behavior on a per job class level
|
17
|
-
other.define_singleton_method(:acidic_by_job_identifier) { @acidic_identifier = :job_id }
|
18
|
-
# You could unique job runs by the arguments passed to the job (e.g. memoization)
|
19
|
-
other.define_singleton_method(:acidic_by_job_arguments) { @acidic_identifier = :job_arguments }
|
20
|
-
# Or, you could unique jobs run by any logic you'd like using a block
|
21
|
-
other.define_singleton_method(:acidic_by) { |&block| @acidic_identifier = block }
|
22
|
-
|
23
|
-
# We add a callback to ensure that staged, non-workflow jobs are "finished" after they are "performed".
|
24
|
-
# This allows us to ensure that we can always inspect whether a run is finished and get correct data
|
25
|
-
other.set_callback :perform, :after, :finish_staged_job, if: -> { was_staged_job? && !was_workflow_job? }
|
26
|
-
# We also allow you to write any of your own callbacks keyed to the "finish" event.
|
27
|
-
# The "finish" event is notably different from the "perform" event for acidic jobs,
|
28
|
-
# as any acidic job can have one or more "perform" event (retries or a resume after awaiting jobs),
|
29
|
-
# but will only ever have one "finish" event (when the run successfully completes)
|
30
|
-
other.define_callbacks :finish
|
31
|
-
end
|
32
|
-
|
33
|
-
included do
|
34
|
-
Mixin.wire_up(self)
|
35
|
-
end
|
36
|
-
|
37
|
-
class_methods do
|
38
|
-
def inherited(subclass)
|
39
|
-
Mixin.wire_up(subclass)
|
40
|
-
super
|
41
|
-
end
|
42
|
-
|
43
|
-
# `perform_now` runs a job synchronously and immediately
|
44
|
-
# `perform_later` runs a job asynchronously and queues it immediately
|
45
|
-
# `perform_acidicly` run a job asynchronously and queues it after a successful database commit
|
46
|
-
def perform_acidicly(*args)
|
47
|
-
job = new(*args)
|
48
|
-
|
49
|
-
Run.stage!(job)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Instantiate an instance of a job ready for serialization
|
53
|
-
def with(...)
|
54
|
-
# New delegation syntax (...) was introduced in Ruby 2.7.
|
55
|
-
# https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
|
56
|
-
job = new(...)
|
57
|
-
# force the job to resolve the `queue_name`, so that we don't try to serialize a Proc into ActiveRecord
|
58
|
-
job.queue_name
|
59
|
-
job
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def idempotency_key
|
64
|
-
IdempotencyKey.new(self).value(acidic_by: acidic_identifier)
|
65
|
-
end
|
66
|
-
|
67
|
-
protected
|
68
|
-
|
69
|
-
# Short circuits execution by sending execution right to 'finished'.
|
70
|
-
# So, ends the job "successfully"
|
71
|
-
def safely_finish_acidic_job
|
72
|
-
FinishedPoint.new
|
73
|
-
end
|
74
|
-
|
75
|
-
def with_acidic_workflow(persisting: {}, &block)
|
76
|
-
raise UnknownJobAdapter unless (defined?(::AcidicJob::Base) && self.class < ::AcidicJob::Base) ||
|
77
|
-
(defined?(::AcidicJob::ActiveKiq) && self.class < ::AcidicJob::ActiveKiq)
|
78
|
-
|
79
|
-
raise RedefiningWorkflow if defined? @workflow_builder
|
80
|
-
|
81
|
-
@workflow_builder = WorkflowBuilder.new
|
82
|
-
|
83
|
-
raise MissingWorkflowBlock, "A block must be passed to `with_acidic_workflow`" unless block_given?
|
84
|
-
raise MissingBlockArgument, "An argument must be passed to the `with_acidic_workflow` block" if block.arity.zero?
|
85
|
-
|
86
|
-
block.call @workflow_builder
|
87
|
-
|
88
|
-
raise NoDefinedSteps if @workflow_builder.steps.empty?
|
89
|
-
|
90
|
-
# convert the array of steps into a hash of recovery_points and next steps
|
91
|
-
workflow = @workflow_builder.define_workflow
|
92
|
-
|
93
|
-
ensure_run_record_and_process(workflow, persisting)
|
94
|
-
rescue LocalJumpError
|
95
|
-
raise MissingWorkflowBlock, "A block must be passed to `with_acidic_workflow`"
|
96
|
-
end
|
97
|
-
|
98
|
-
# DEPRECATED
|
99
|
-
# second attempt at the DSL, but `providing` suggested you needed to serialize
|
100
|
-
# any data that would be used in step methods, but simply instance variables work.
|
101
|
-
def with_acidity(providing: {}, &block)
|
102
|
-
::ActiveSupport::Deprecation.new("1.0", "AcidicJob").deprecation_warning(:with_acidity)
|
103
|
-
|
104
|
-
@workflow_builder = WorkflowBuilder.new
|
105
|
-
@workflow_builder.instance_exec(&block)
|
106
|
-
|
107
|
-
raise NoDefinedSteps if @workflow_builder.steps.empty?
|
108
|
-
|
109
|
-
# convert the array of steps into a hash of recovery_points and next steps
|
110
|
-
workflow = @workflow_builder.define_workflow
|
111
|
-
|
112
|
-
ensure_run_record_and_process(workflow, providing)
|
113
|
-
rescue LocalJumpError
|
114
|
-
raise MissingWorkflowBlock, "A block must be passed to `with_acidity`"
|
115
|
-
end
|
116
|
-
|
117
|
-
# DEPRECATED
|
118
|
-
# first attempt at the DSL, but `idempotently` and `with` are too distant from the gem name.
|
119
|
-
def idempotently(with: {}, &block)
|
120
|
-
::ActiveSupport::Deprecation.new("1.0", "AcidicJob").deprecation_warning(:idempotently)
|
121
|
-
|
122
|
-
@workflow_builder = WorkflowBuilder.new
|
123
|
-
@workflow_builder.instance_exec(&block)
|
124
|
-
|
125
|
-
raise NoDefinedSteps if @workflow_builder.steps.empty?
|
126
|
-
|
127
|
-
# convert the array of steps into a hash of recovery_points and next steps
|
128
|
-
workflow = @workflow_builder.define_workflow
|
129
|
-
|
130
|
-
ensure_run_record_and_process(workflow, with)
|
131
|
-
rescue LocalJumpError
|
132
|
-
raise MissingWorkflowBlock, "A block must be passed to `idempotently`"
|
133
|
-
end
|
134
|
-
|
135
|
-
private
|
136
|
-
|
137
|
-
# You can always retrieve the unique identifier from within the job instance itself
|
138
|
-
def acidic_identifier
|
139
|
-
self.class.instance_variable_get(:@acidic_identifier)
|
140
|
-
end
|
141
|
-
|
142
|
-
def ensure_run_record_and_process(workflow, persisting)
|
143
|
-
::AcidicJob.logger.log_run_event("Initializing run...", self, nil)
|
144
|
-
@acidic_job_run = ::ActiveRecord::Base.transaction(isolation: acidic_isolation_level) do
|
145
|
-
run = Run.find_by(idempotency_key: idempotency_key)
|
146
|
-
serialized_job = serialize
|
147
|
-
|
148
|
-
if run.present?
|
149
|
-
# Programs enqueuing multiple jobs with different parameters but the
|
150
|
-
# same idempotency key is a bug.
|
151
|
-
if run.serialized_job["arguments"] != serialized_job["arguments"]
|
152
|
-
raise MismatchedIdempotencyKeyAndJobArguments
|
153
|
-
end
|
154
|
-
|
155
|
-
# Only acquire a lock if the key is unlocked or its lock has expired
|
156
|
-
# because the original job was long enough ago.
|
157
|
-
raise LockedIdempotencyKey if run.lock_active?
|
158
|
-
|
159
|
-
run.update!(
|
160
|
-
last_run_at: Time.current,
|
161
|
-
locked_at: Time.current,
|
162
|
-
# staged workflow jobs won't have the `workflow` or `recovery_point` stored when staged,
|
163
|
-
# so we need to persist both here.
|
164
|
-
workflow: workflow,
|
165
|
-
recovery_point: run.recovery_point || workflow.keys.first
|
166
|
-
)
|
167
|
-
else
|
168
|
-
run = Run.create!(
|
169
|
-
staged: false,
|
170
|
-
idempotency_key: idempotency_key,
|
171
|
-
job_class: self.class.name,
|
172
|
-
locked_at: Time.current,
|
173
|
-
last_run_at: Time.current,
|
174
|
-
workflow: workflow,
|
175
|
-
recovery_point: workflow.keys.first,
|
176
|
-
serialized_job: serialize
|
177
|
-
)
|
178
|
-
end
|
179
|
-
|
180
|
-
# persist `persisting` values and set accessors for each
|
181
|
-
# first, get the current state of all accessors for both previously persisted and initialized values
|
182
|
-
current_accessors = persisting.stringify_keys.merge(run.attr_accessors)
|
183
|
-
|
184
|
-
# next, ensure that `Run#attr_accessors` is populated with initial values
|
185
|
-
# skip validations for this call to ensure a write
|
186
|
-
run.update_column(:attr_accessors, current_accessors) if current_accessors != run.attr_accessors
|
187
|
-
|
188
|
-
# finally, set reader and writer methods
|
189
|
-
current_accessors.each do |accessor, value|
|
190
|
-
# the reader method may already be defined
|
191
|
-
self.class.attr_reader accessor unless respond_to?(accessor)
|
192
|
-
# but we should always update the value to match the current value
|
193
|
-
instance_variable_set("@#{accessor}", value)
|
194
|
-
# and we overwrite the setter to ensure any updates to an accessor update the `Run` stored value
|
195
|
-
# Note: we must define the singleton method on the instance to avoid overwriting setters on other
|
196
|
-
# instances of the same class
|
197
|
-
define_singleton_method("#{accessor}=") do |updated_value|
|
198
|
-
instance_variable_set("@#{accessor}", updated_value)
|
199
|
-
run.attr_accessors[accessor] = updated_value
|
200
|
-
run.save!(validate: false)
|
201
|
-
updated_value
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
# ensure that we return the `run` record so that the result of this block is the record
|
206
|
-
run
|
207
|
-
end
|
208
|
-
::AcidicJob.logger.log_run_event("Initialized run.", self, @acidic_job_run)
|
209
|
-
|
210
|
-
Processor.new(@acidic_job_run, self).process_run
|
211
|
-
end
|
212
|
-
|
213
|
-
def was_staged_job?
|
214
|
-
job_id.start_with? Run::STAGED_JOB_ID_PREFIX
|
215
|
-
end
|
216
|
-
|
217
|
-
def was_workflow_job?
|
218
|
-
return false unless defined? @acidic_job_run
|
219
|
-
|
220
|
-
@acidic_job_run.present?
|
221
|
-
end
|
222
|
-
|
223
|
-
def staged_job_run
|
224
|
-
return unless was_staged_job?
|
225
|
-
# memoize so we don't have to make unnecessary database calls
|
226
|
-
return @staged_job_run if defined? @staged_job_run
|
227
|
-
|
228
|
-
# "STG__#{idempotency_key}__#{encoded_global_id}"
|
229
|
-
_prefix, _idempotency_key, encoded_global_id = job_id.split("__")
|
230
|
-
staged_job_gid = "gid://#{::Base64.urlsafe_decode64(encoded_global_id)}"
|
231
|
-
|
232
|
-
@staged_job_run = ::GlobalID::Locator.locate(staged_job_gid)
|
233
|
-
end
|
234
|
-
|
235
|
-
def finish_staged_job
|
236
|
-
staged_job_run.finish!
|
237
|
-
end
|
238
|
-
|
239
|
-
def acidic_isolation_level
|
240
|
-
case ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym
|
241
|
-
# SQLite doesn't support `serializable` transactions,
|
242
|
-
# so we use the strictest isolation_level is does support
|
243
|
-
when :sqlite
|
244
|
-
:read_uncommitted
|
245
|
-
else
|
246
|
-
:serializable
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
data/lib/acidic_job/processor.rb
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AcidicJob
|
4
|
-
class Processor
|
5
|
-
def initialize(run, job)
|
6
|
-
@run = run
|
7
|
-
@job = job
|
8
|
-
@workflow = Workflow.new(run, job)
|
9
|
-
end
|
10
|
-
|
11
|
-
def process_run
|
12
|
-
# if the run record is already marked as finished, immediately return its result
|
13
|
-
return @run.succeeded? if @run.finished?
|
14
|
-
|
15
|
-
AcidicJob.logger.log_run_event("Processing #{@run.current_step_name}...", @job, @run)
|
16
|
-
loop do
|
17
|
-
break if @run.finished?
|
18
|
-
|
19
|
-
if !@run.known_recovery_point?
|
20
|
-
raise UnknownRecoveryPoint,
|
21
|
-
"Defined workflow does not reference this step: #{@run.current_step_name.inspect}"
|
22
|
-
elsif (awaited_jobs = jobs_from(@run.current_step_awaits)).any?
|
23
|
-
# We only execute the current step, without progressing to the next step.
|
24
|
-
# This ensures that any failures in parallel jobs will have this step retried in the main workflow
|
25
|
-
step_result = @workflow.execute_current_step
|
26
|
-
# We allow the `#step_done` method to manage progressing the recovery_point to the next step,
|
27
|
-
# and then calling `process_run` to restart the main workflow on the next step.
|
28
|
-
# We pass the `step_result` so that the async callback called after the step-parallel-jobs complete
|
29
|
-
# can move on to the appropriate next stage in the workflow.
|
30
|
-
enqueue_awaited_jobs(awaited_jobs, step_result)
|
31
|
-
# after processing the current step, break the processing loop
|
32
|
-
# and stop this method from blocking in the primary worker
|
33
|
-
# as it will continue once the background workers all succeed
|
34
|
-
# so we want to keep the primary worker queue free to process new work
|
35
|
-
# this CANNOT ever be `break` as that wouldn't exit the parent job,
|
36
|
-
# only this step in the workflow, blocking as it awaits the next step
|
37
|
-
return true
|
38
|
-
else
|
39
|
-
@workflow.execute_current_step
|
40
|
-
@workflow.progress_to_next_step
|
41
|
-
end
|
42
|
-
end
|
43
|
-
AcidicJob.logger.log_run_event("Processed #{@run.current_step_name}.", @job, @run)
|
44
|
-
|
45
|
-
@run.succeeded?
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def enqueue_awaited_jobs(awaited_jobs, step_result)
|
51
|
-
AcidicJob.logger.log_run_event("Enqueuing #{awaited_jobs.count} awaited jobs...", @job, @run)
|
52
|
-
# All jobs created in the block are pushed atomically at the end of the block.
|
53
|
-
AcidicJob::Run.transaction do
|
54
|
-
awaited_jobs.each do |awaited_job|
|
55
|
-
worker_class, args = job_and_args(awaited_job)
|
56
|
-
|
57
|
-
job = worker_class.new(*args)
|
58
|
-
|
59
|
-
AcidicJob::Run.await!(job, by: @run, return_to: step_result)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
AcidicJob.logger.log_run_event("Enqueued #{awaited_jobs.count} awaited jobs.", @job, @run)
|
63
|
-
end
|
64
|
-
|
65
|
-
def jobs_from(jobs_or_jobs_getter)
|
66
|
-
case jobs_or_jobs_getter
|
67
|
-
when Array
|
68
|
-
jobs_or_jobs_getter.compact
|
69
|
-
when Symbol, String
|
70
|
-
if @job.respond_to?(jobs_or_jobs_getter, _include_private = true)
|
71
|
-
jobs = @job.method(jobs_or_jobs_getter).call
|
72
|
-
Array(jobs).compact
|
73
|
-
else
|
74
|
-
raise UnknownAwaitedJob,
|
75
|
-
"Invalid `awaits`; unknown method `#{jobs_or_jobs_getter}` for this job"
|
76
|
-
end
|
77
|
-
else
|
78
|
-
raise UnknownAwaitedJob,
|
79
|
-
"Invalid `awaits`; must be either an jobs Array or method name, was: #{jobs_or_jobs_getter.class.name}"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def job_and_args(job)
|
84
|
-
case job
|
85
|
-
when Class
|
86
|
-
[job, []]
|
87
|
-
else
|
88
|
-
[
|
89
|
-
job.class,
|
90
|
-
job.arguments
|
91
|
-
]
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
data/lib/acidic_job/rails.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "rails/railtie"
|
4
|
-
|
5
|
-
module AcidicJob
|
6
|
-
class Rails < ::Rails::Railtie
|
7
|
-
initializer "acidic_job.action_mailer_extension" do
|
8
|
-
::ActiveSupport.on_load(:action_mailer) do
|
9
|
-
# Add `deliver_acidicly` to ActionMailer
|
10
|
-
::ActionMailer::Parameterized::MessageDelivery.include(Extensions::ActionMailer)
|
11
|
-
::ActionMailer::MessageDelivery.include(Extensions::ActionMailer)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
initializer "acidic_job.active_job_serializers" do
|
16
|
-
::ActiveSupport.on_load(:active_job) do
|
17
|
-
::ActiveJob::Serializers.add_serializers(
|
18
|
-
Serializers::ExceptionSerializer,
|
19
|
-
Serializers::FinishedPointSerializer,
|
20
|
-
Serializers::JobSerializer,
|
21
|
-
Serializers::RangeSerializer,
|
22
|
-
Serializers::RecoveryPointSerializer,
|
23
|
-
Serializers::WorkerSerializer
|
24
|
-
)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
generators do
|
29
|
-
require "generators/acidic_job/install_generator"
|
30
|
-
end
|
31
|
-
|
32
|
-
# This hook happens after all initializers are run, just before returning
|
33
|
-
config.after_initialize do
|
34
|
-
if defined?(::Noticed)
|
35
|
-
# Add `deliver_acidicly` to Noticed
|
36
|
-
::Noticed::Base.include(Extensions::Noticed)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
|
5
|
-
module AcidicJob
|
6
|
-
class Serializer
|
7
|
-
# Used for `serialize` method in ActiveRecord
|
8
|
-
class << self
|
9
|
-
def load(json)
|
10
|
-
return if json.nil? || json.empty?
|
11
|
-
|
12
|
-
data = JSON.parse(json)
|
13
|
-
Arguments.send :deserialize_argument, data
|
14
|
-
end
|
15
|
-
|
16
|
-
def dump(obj)
|
17
|
-
data = Arguments.send :serialize_argument, obj
|
18
|
-
data.to_json
|
19
|
-
rescue ActiveJob::SerializationError
|
20
|
-
raise UnserializableValue
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_job/serializers/object_serializer"
|
4
|
-
|
5
|
-
module AcidicJob
|
6
|
-
module Serializers
|
7
|
-
class ExceptionSerializer < ::ActiveJob::Serializers::ObjectSerializer
|
8
|
-
def serialize(exception)
|
9
|
-
hash = {
|
10
|
-
"class" => exception.class.name,
|
11
|
-
"message" => exception.message,
|
12
|
-
"cause" => exception.cause,
|
13
|
-
"backtrace" => {}
|
14
|
-
}
|
15
|
-
|
16
|
-
exception.backtrace.map do |trace|
|
17
|
-
path, _, location = trace.rpartition("/")
|
18
|
-
|
19
|
-
next if hash["backtrace"].key?(path)
|
20
|
-
|
21
|
-
hash["backtrace"][path] = location
|
22
|
-
end
|
23
|
-
|
24
|
-
super(hash)
|
25
|
-
end
|
26
|
-
|
27
|
-
def deserialize(hash)
|
28
|
-
exception_class = hash["class"].constantize
|
29
|
-
exception = exception_class.new(hash["message"])
|
30
|
-
exception.set_backtrace(hash["backtrace"].map do |path, location|
|
31
|
-
[path, location].join("/")
|
32
|
-
end)
|
33
|
-
exception
|
34
|
-
end
|
35
|
-
|
36
|
-
def serialize?(argument)
|
37
|
-
defined?(Exception) && argument.is_a?(Exception)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_job/serializers/object_serializer"
|
4
|
-
|
5
|
-
module AcidicJob
|
6
|
-
module Serializers
|
7
|
-
class FinishedPointSerializer < ::ActiveJob::Serializers::ObjectSerializer
|
8
|
-
def serialize(finished_point)
|
9
|
-
super(
|
10
|
-
"class" => finished_point.class.name
|
11
|
-
)
|
12
|
-
end
|
13
|
-
|
14
|
-
def deserialize(hash)
|
15
|
-
finished_point_class = hash["class"].constantize
|
16
|
-
finished_point_class.new
|
17
|
-
end
|
18
|
-
|
19
|
-
def serialize?(argument)
|
20
|
-
defined?(::AcidicJob::FinishedPoint) && argument.is_a?(::AcidicJob::FinishedPoint)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_job/serializers/object_serializer"
|
4
|
-
|
5
|
-
module AcidicJob
|
6
|
-
module Serializers
|
7
|
-
class JobSerializer < ::ActiveJob::Serializers::ObjectSerializer
|
8
|
-
def serialize(job)
|
9
|
-
# don't serialize the `enqueued_at` value, as ActiveRecord will check if the Run record has changed
|
10
|
-
# by comparing the deserialized database value with a temporary in-memory generated value.
|
11
|
-
# That temporary in-memory generated value can sometimes have an `enqueued_at` value that is 1 second off
|
12
|
-
# from the original. In this case, ActiveRecord will think the record has unsaved changes and block the lock.
|
13
|
-
super(job.as_json.merge("job_class" => job.class.name))
|
14
|
-
end
|
15
|
-
|
16
|
-
def deserialize(hash)
|
17
|
-
job = ::ActiveJob::Base.deserialize(hash)
|
18
|
-
job.send(:deserialize_arguments_if_needed)
|
19
|
-
job
|
20
|
-
end
|
21
|
-
|
22
|
-
def serialize?(argument)
|
23
|
-
defined?(::ActiveJob::Base) && argument.class < ::ActiveJob::Base
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_job/serializers/object_serializer"
|
4
|
-
|
5
|
-
# :nocov:
|
6
|
-
module AcidicJob
|
7
|
-
module Serializers
|
8
|
-
class RangeSerializer < ::ActiveJob::Serializers::ObjectSerializer
|
9
|
-
KEYS = %w[begin end exclude_end].freeze
|
10
|
-
|
11
|
-
def serialize(range)
|
12
|
-
args = Arguments.serialize([range.begin, range.end, range.exclude_end?])
|
13
|
-
super(KEYS.zip(args).to_h)
|
14
|
-
end
|
15
|
-
|
16
|
-
def deserialize(hash)
|
17
|
-
klass.new(*Arguments.deserialize(hash.values_at(*KEYS)))
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def klass
|
23
|
-
::Range
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
# :nocov:
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_job/serializers/object_serializer"
|
4
|
-
|
5
|
-
module AcidicJob
|
6
|
-
module Serializers
|
7
|
-
class RecoveryPointSerializer < ::ActiveJob::Serializers::ObjectSerializer
|
8
|
-
def serialize(recovery_point)
|
9
|
-
super(
|
10
|
-
"class" => recovery_point.class.name,
|
11
|
-
"name" => recovery_point.name
|
12
|
-
)
|
13
|
-
end
|
14
|
-
|
15
|
-
def deserialize(hash)
|
16
|
-
recovery_point_class = hash["class"].constantize
|
17
|
-
recovery_point_class.new(hash["name"])
|
18
|
-
end
|
19
|
-
|
20
|
-
def serialize?(argument)
|
21
|
-
defined?(::AcidicJob::RecoveryPoint) && argument.is_a?(::AcidicJob::RecoveryPoint)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_job/serializers/object_serializer"
|
4
|
-
|
5
|
-
# :nocov:
|
6
|
-
module AcidicJob
|
7
|
-
module Serializers
|
8
|
-
class WorkerSerializer < ::ActiveJob::Serializers::ObjectSerializer
|
9
|
-
def serialize(worker)
|
10
|
-
super(
|
11
|
-
"job_class" => worker.class.name,
|
12
|
-
"arguments" => worker.arguments,
|
13
|
-
)
|
14
|
-
end
|
15
|
-
|
16
|
-
def deserialize(hash)
|
17
|
-
worker_class = hash["job_class"].constantize
|
18
|
-
worker_class.new(*hash["arguments"])
|
19
|
-
end
|
20
|
-
|
21
|
-
def serialize?(argument)
|
22
|
-
defined?(::Sidekiq) && argument.class.include?(::Sidekiq::Worker)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
# :nocov:
|
data/lib/acidic_job/test_case.rb
DELETED
data/lib/acidic_job/testing.rb
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "database_cleaner"
|
4
|
-
|
5
|
-
module AcidicJob
|
6
|
-
module Testing
|
7
|
-
def self.included(mod)
|
8
|
-
mod.class_eval "self.use_transactional_tests = false if respond_to?(:use_transactional_tests)", __FILE__, __LINE__
|
9
|
-
end
|
10
|
-
|
11
|
-
def before_setup
|
12
|
-
@connection = ::ActiveRecord::Base.connection
|
13
|
-
@original_cleaners = ::DatabaseCleaner.cleaners
|
14
|
-
::DatabaseCleaner.cleaners = transaction_free_cleaners_for(@original_cleaners)
|
15
|
-
super
|
16
|
-
::DatabaseCleaner.start
|
17
|
-
end
|
18
|
-
|
19
|
-
def after_teardown
|
20
|
-
::DatabaseCleaner.clean
|
21
|
-
super
|
22
|
-
::DatabaseCleaner.cleaners = @original_cleaners
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
# Ensure that the system's original DatabaseCleaner configuration is maintained, options included,
|
28
|
-
# except that any `transaction` strategies for any ORMs are replaced with a `deletion` strategy.
|
29
|
-
def transaction_free_cleaners_for(original_cleaners)
|
30
|
-
non_transaction_cleaners = original_cleaners.dup.to_h do |(orm, opts), cleaner|
|
31
|
-
[[orm, opts], ensure_no_transaction_strategies_for(cleaner)]
|
32
|
-
end
|
33
|
-
::DatabaseCleaner::Cleaners.new(non_transaction_cleaners)
|
34
|
-
end
|
35
|
-
|
36
|
-
def ensure_no_transaction_strategies_for(cleaner)
|
37
|
-
return cleaner unless strategy_name_for(cleaner) == "transaction"
|
38
|
-
|
39
|
-
cleaner.strategy = deletion_strategy_for(cleaner)
|
40
|
-
cleaner
|
41
|
-
end
|
42
|
-
|
43
|
-
def strategy_name_for(cleaner)
|
44
|
-
cleaner # <DatabaseCleaner::Cleaner>
|
45
|
-
.strategy # <DatabaseCleaner::ActiveRecord::Truncation>
|
46
|
-
.class # DatabaseCleaner::ActiveRecord::Truncation
|
47
|
-
.name # "DatabaseCleaner::ActiveRecord::Truncation"
|
48
|
-
.rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
|
49
|
-
.last # "Truncation"
|
50
|
-
.downcase # "truncation"
|
51
|
-
end
|
52
|
-
|
53
|
-
def deletion_strategy_for(cleaner)
|
54
|
-
strategy = cleaner.strategy
|
55
|
-
strategy_namespace = strategy # <DatabaseCleaner::ActiveRecord::Truncation>
|
56
|
-
.class # DatabaseCleaner::ActiveRecord::Truncation
|
57
|
-
.name # "DatabaseCleaner::ActiveRecord::Truncation"
|
58
|
-
.rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
|
59
|
-
.first # "DatabaseCleaner::ActiveRecord"
|
60
|
-
deletion_strategy_class_name = [strategy_namespace, "::", "Deletion"].join
|
61
|
-
deletion_strategy_class = deletion_strategy_class_name.constantize
|
62
|
-
instance_variable_hash = strategy.instance_variables.to_h do |var|
|
63
|
-
[
|
64
|
-
var.to_s.remove("@"),
|
65
|
-
strategy.instance_variable_get(var)
|
66
|
-
]
|
67
|
-
end
|
68
|
-
options = instance_variable_hash.except("db", "connection_class")
|
69
|
-
|
70
|
-
deletion_strategy_class.new(**options)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|