Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions app/models/solid_queue/batch_execution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ def create_all_from_jobs(jobs)
end
end

# Shared by the tracking row's own destroy callback and by a finishing
# job, which deletes its row by key and checks from its own after_commit.
def finish_batch_for(batch_id)
# Every finishing job asks whether it was the last one, and almost never
# is. Asking the cheap question first means the batch row is only read
# for the job that actually finishes the batch. #finish asks it again, so
# this decides nothing on its own.
return if where(batch_id: batch_id).exists?

# Skip the serialized callback and metadata columns on this hot path
if batch = Batch.select(:id, :finished_at, :enqueued_at).find_by(id: batch_id)
batch.finish
end
end

private
def attempt_to_update_total_jobs(batch_id, jobs)
new_jobs_count = count_new_jobs_among(jobs)
Expand All @@ -43,16 +58,7 @@ def count_new_jobs_among(jobs)

private
def finish_batch
# Every finishing job asks whether it was the last one, and almost never
# is. Asking the cheap question first means the batch row is only read
# for the job that actually finishes the batch. #finish asks it again, so
# this decides nothing on its own.
return if self.class.where(batch_id: batch_id).exists?

# Skip the serialized callback and metadata columns on this hot path
if batch = Batch.select(:id, :finished_at, :enqueued_at).find_by(id: batch_id)
batch.finish
end
self.class.finish_batch_for(batch_id)
end
end
end
16 changes: 15 additions & 1 deletion app/models/solid_queue/job/batchable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Batchable

after_create :create_batch_execution, if: :batched?
after_update :update_batch_progress, if: :batched?
after_commit :finish_batch_after_progress, on: :update
before_destroy :destroy_batch_execution, if: :batched?
end

Expand All @@ -31,10 +32,23 @@ def create_batch_execution
BatchExecution.create_all_from_jobs([ self ])
end

# The tracking row is deleted by key rather than loaded and destroyed:
# a finishing job already knows which row is its own. Completion is then
# checked from this job's own after_commit, which runs at the same point
# the tracking row's would have.
def update_batch_progress
return unless saved_change_to_finished_at? && finished_at.present?

batch_execution&.destroy!
@batch_execution_removed = BatchExecution.where(job_id: id).delete_all.positive?
rescue ActiveRecord::ActiveRecordError => e
SolidQueue.instrument(:batch_progress_error, batch_id: batch_id, job_id: id, error: e)
end

def finish_batch_after_progress
return unless @batch_execution_removed

@batch_execution_removed = false
BatchExecution.finish_batch_for(batch_id)
rescue ActiveRecord::ActiveRecordError => e
SolidQueue.instrument(:batch_progress_error, batch_id: batch_id, job_id: id, error: e)
end
Expand Down