@@ -7,10 +7,12 @@ class JobBatch < Record
7
7
8
8
serialize :on_finish_active_job , coder : JSON
9
9
serialize :on_success_active_job , coder : JSON
10
+ serialize :on_failure_active_job , coder : JSON
10
11
11
12
scope :incomplete , -> {
12
13
where ( finished_at : nil ) . where ( "changed_at IS NOT NULL OR last_changed_at < ?" , 1 . hour . ago )
13
14
}
15
+ scope :finished , -> { where . not ( finished_at : nil ) }
14
16
15
17
class << self
16
18
def current_batch_id
@@ -45,6 +47,7 @@ def dispatch_finished_batches
45
47
def batch_attributes ( attributes )
46
48
on_finish_klass = attributes . delete ( :on_finish )
47
49
on_success_klass = attributes . delete ( :on_success )
50
+ on_failure_klass = attributes . delete ( :on_failure )
48
51
49
52
if on_finish_klass . present?
50
53
attributes [ :on_finish_active_job ] = as_active_job ( on_finish_klass ) . serialize
@@ -54,6 +57,10 @@ def batch_attributes(attributes)
54
57
attributes [ :on_success_active_job ] = as_active_job ( on_success_klass ) . serialize
55
58
end
56
59
60
+ if on_failure_klass . present?
61
+ attributes [ :on_failure_active_job ] = as_active_job ( on_failure_klass ) . serialize
62
+ end
63
+
57
64
attributes
58
65
end
59
66
@@ -69,29 +76,51 @@ def finished?
69
76
def finish
70
77
return if finished?
71
78
reset_changed_at
72
- jobs . find_each do |next_job |
73
- # FIXME: If it's failed but is going to retry, how do we know?
74
- # Because we need to know if we will determine what the failed execution means
75
- # FIXME: use "success" vs "finish" vs "discard" `completion_type` to determine
76
- # how to analyze each job
77
- return unless next_job . finished?
78
- end
79
79
80
+ all_jobs_succeeded = true
80
81
attrs = { }
82
+ jobs . find_each do |next_job |
83
+ # SolidQueue does treats `discard_on` differently than failures. The job will report as being :finished,
84
+ # and there is no record of the failure.
85
+ # GoodJob would report a discard as an error. It's possible we should do that in the future?
86
+ if fire_failure_job? ( next_job )
87
+ perform_completion_job ( :on_failure_active_job , attrs )
88
+ update! ( attrs )
89
+ end
90
+
91
+ status = next_job . status
92
+ all_jobs_succeeded = all_jobs_succeeded && status != :failed
93
+ return unless status . in? ( [ :finished , :failed ] )
94
+ end
81
95
82
96
if on_finish_active_job . present?
83
- active_job = ActiveJob :: Base . deserialize ( on_finish_active_job )
84
- active_job . send ( :deserialize_arguments_if_needed )
85
- active_job . arguments = [ self ] + Array . wrap ( active_job . arguments )
86
- ActiveJob . perform_all_later ( [ active_job ] )
87
- attrs [ :job ] = Job . find_by ( active_job_id : active_job . job_id )
97
+ perform_completion_job ( : on_finish_active_job, attrs )
98
+ end
99
+
100
+ if on_success_active_job . present? && all_jobs_succeeded
101
+ perform_completion_job ( :on_success_active_job , attrs )
88
102
end
89
103
90
104
update! ( { finished_at : Time . zone . now } . merge ( attrs ) )
91
105
end
92
106
93
107
private
94
108
109
+ def fire_failure_job? ( job )
110
+ return false if on_failure_active_job . blank? || job . failed_execution . blank?
111
+ job = ActiveJob ::Base . deserialize ( on_failure_active_job )
112
+ job . provider_job_id . blank?
113
+ end
114
+
115
+ def perform_completion_job ( job_field , attrs )
116
+ active_job = ActiveJob ::Base . deserialize ( send ( job_field ) )
117
+ active_job . send ( :deserialize_arguments_if_needed )
118
+ active_job . arguments = [ self ] + Array . wrap ( active_job . arguments )
119
+ ActiveJob . perform_all_later ( [ active_job ] )
120
+ active_job . provider_job_id = Job . find_by ( active_job_id : active_job . job_id ) . id
121
+ attrs [ job_field ] = active_job . serialize
122
+ end
123
+
95
124
def reset_changed_at
96
125
if changed_at . blank? && last_changed_at . present?
97
126
update_columns ( last_changed_at : Time . zone . now ) # wait another hour before we check again
0 commit comments