Skip to content

Commit d3e0b9f

Browse files
committed
Warn when a stop event is skipped for a task that was running
Cloud Controller has always skipped writing the TASK_STOPPED usage event when a task finishes without a start event on record. The earlier commits on this branch keep that rule and apply it on the destroy path too. The skip is correct -- a stop event with no start event is a record consumers cannot use -- but it has always been silent. This commit does not fix a bug in the earlier commits. It surfaces information about the long-standing skip: when the skip fires for a task that was RUNNING, log a warning. A task can only reach RUNNING through the state change that also writes its TASK_STARTED event, in the same transaction. So a running task with no start record means something deleted the record later: the destructive v2 purge, or the events cleanup job of an older version, which deleted the start events of long-running tasks. Someone should find out which one happened, and the warning names the repair: run 'rake db:was_running_backfill'. The warning fires only when the task was RUNNING, because only then is it certain the task ran. A task canceled while still PENDING also passes through CANCELING, so a CANCELING task with no start record may simply never have run. Warning there would be noise.
1 parent cf3d700 commit d3e0b9f

2 files changed

Lines changed: 130 additions & 6 deletions

File tree

app/models/runtime/task_model.rb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,38 @@ def create_stop_event_if_needed
155155
Repositories::AppUsageEventRepository::TASK_WAS_RUNNING_EVENT_STATE
156156
]
157157
started = app_usage_repo.find_by_task_and_state(task: self, state: start_evidence_states)
158-
return if started.nil?
158+
if started.nil?
159+
warn_if_running_task_has_no_start_record
160+
return
161+
end
159162

160163
create_stop_event
161164
end
162165

163166
def create_stop_event
164167
Repositories::AppUsageEventRepository.new.create_from_task(self, 'TASK_STOPPED')
165168
end
169+
170+
# The skip above is silent. That is fine for a task that never ran. But a
171+
# task can only reach RUNNING through the state change that also writes
172+
# its TASK_STARTED event, in the same transaction. So a running task with
173+
# no start record means something deleted the record later: the
174+
# destructive v2 purge, or the events cleanup job of an older version
175+
# that did not yet keep the start events of running tasks. Someone should
176+
# find out which, so log a warning. We warn only for RUNNING because only
177+
# RUNNING is certain: a task canceled while still PENDING also passes
178+
# through CANCELING, so a CANCELING task may never have run.
179+
def warn_if_running_task_has_no_start_record
180+
state_before_stop = column_changed?(:state) ? initial_value(:state) : state
181+
return unless state_before_stop == RUNNING_STATE
182+
183+
logger.warn("Not writing a TASK_STOPPED usage event for task #{guid}: the task was running but has no " \
184+
'TASK_STARTED or TASK_WAS_RUNNING usage event on record. This should not happen. Run ' \
185+
"'rake db:was_running_backfill' and check whether usage events were deleted or purged.")
186+
end
187+
188+
def logger
189+
@logger ||= Steno.logger('cc.models.task')
190+
end
166191
end
167192
end

spec/unit/models/runtime/task_model_spec.rb

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,28 @@ module VCAP::CloudController
4444

4545
context 'when there is neither a TASK_STARTED event nor a TASK_WAS_RUNNING baseline' do
4646
let!(:start_event) { nil }
47+
let(:fake_logger) { instance_double(Steno::Logger, info: nil, warn: nil, debug: nil, error: nil) }
48+
49+
before do
50+
allow(Steno).to receive(:logger).and_return(fake_logger)
51+
end
4752

4853
it 'does not create a TASK_STOPPED event, since no consumer ever saw the task start' do
4954
task.update(state: TaskModel::SUCCEEDED_STATE)
5055

5156
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
5257
expect(event).to be_nil
5358
end
59+
60+
it 'logs a warning, because a task that reached RUNNING must have had a start record once' do
61+
task.update(state: TaskModel::SUCCEEDED_STATE)
62+
63+
expect(fake_logger).to have_received(:warn).with(
64+
"Not writing a TASK_STOPPED usage event for task #{task.guid}: the task was running but has no " \
65+
'TASK_STARTED or TASK_WAS_RUNNING usage event on record. This should not happen. Run ' \
66+
"'rake db:was_running_backfill' and check whether usage events were deleted or purged."
67+
)
68+
end
5469
end
5570
end
5671

@@ -81,25 +96,51 @@ module VCAP::CloudController
8196

8297
context 'when there is neither a TASK_STARTED event nor a TASK_WAS_RUNNING baseline' do
8398
let!(:start_event) { nil }
99+
let(:fake_logger) { instance_double(Steno::Logger, info: nil, warn: nil, debug: nil, error: nil) }
100+
101+
before do
102+
allow(Steno).to receive(:logger).and_return(fake_logger)
103+
end
84104

85105
it 'does not create a TASK_STOPPED event, since no consumer ever saw the task start' do
86106
task.update(state: TaskModel::FAILED_STATE)
87107

88108
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
89109
expect(event).to be_nil
90110
end
111+
112+
it 'logs a warning, because a task that reached RUNNING must have had a start record once' do
113+
task.update(state: TaskModel::FAILED_STATE)
114+
115+
expect(fake_logger).to have_received(:warn).with(
116+
"Not writing a TASK_STOPPED usage event for task #{task.guid}: the task was running but has no " \
117+
'TASK_STARTED or TASK_WAS_RUNNING usage event on record. This should not happen. Run ' \
118+
"'rake db:was_running_backfill' and check whether usage events were deleted or purged."
119+
)
120+
end
91121
end
92122
end
93123

94124
context 'when the task is moving from the PENDING state' do
95125
let(:task) { create(:task_model, app: parent_app, state: TaskModel::PENDING_STATE) }
126+
let(:fake_logger) { instance_double(Steno::Logger, info: nil, warn: nil, debug: nil, error: nil) }
127+
128+
before do
129+
allow(Steno).to receive(:logger).and_return(fake_logger)
130+
end
96131

97132
it 'does not create a TASK_STOPPED event' do
98133
task.update(state: TaskModel::FAILED_STATE)
99134

100135
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
101136
expect(event).to be_nil
102137
end
138+
139+
it 'does not log a warning, because a task that never ran is not supposed to have a start record' do
140+
task.update(state: TaskModel::FAILED_STATE)
141+
142+
expect(fake_logger).not_to have_received(:warn)
143+
end
103144
end
104145

105146
context 'when the task is moving from the CANCELING state' do
@@ -131,13 +172,25 @@ module VCAP::CloudController
131172

132173
context 'when the task does not have a TASK_STARTED event' do
133174
let!(:start_event) { nil }
175+
let(:fake_logger) { instance_double(Steno::Logger, info: nil, warn: nil, debug: nil, error: nil) }
176+
177+
before do
178+
allow(Steno).to receive(:logger).and_return(fake_logger)
179+
end
134180

135181
it 'does not create a TASK_STOPPED event' do
136182
task.update(state: TaskModel::FAILED_STATE)
137183

138184
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
139185
expect(event).to be_nil
140186
end
187+
188+
it 'does not log a warning, because a task canceled while PENDING also passes through CANCELING, ' \
189+
'so we cannot be sure this task ever ran' do
190+
task.update(state: TaskModel::FAILED_STATE)
191+
192+
expect(fake_logger).not_to have_received(:warn)
193+
end
141194
end
142195
end
143196

@@ -203,24 +256,70 @@ module VCAP::CloudController
203256

204257
context 'when the TASK_STARTED event has been pruned and a TASK_WAS_RUNNING baseline exists' do
205258
let!(:start_event) { create(:app_usage_event, task_guid: task.guid, state: 'TASK_WAS_RUNNING') }
259+
let(:fake_logger) { instance_double(Steno::Logger, info: nil, warn: nil, debug: nil, error: nil) }
260+
261+
before do
262+
allow(Steno).to receive(:logger).and_return(fake_logger)
263+
end
206264

207265
it 'still creates a TASK_STOPPED event' do
208266
task.destroy
209267

210268
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
211269
expect(event).not_to be_nil
212270
end
271+
272+
it 'does not log a warning, because the stop event was written' do
273+
task.destroy
274+
275+
expect(fake_logger).not_to have_received(:warn)
276+
end
213277
end
214278

215279
context 'when there is neither a TASK_STARTED event nor a TASK_WAS_RUNNING baseline' do
216-
let(:task) { create(:task_model, app: parent_app, state: TaskModel::PENDING_STATE) }
217280
let!(:start_event) { nil }
281+
let(:fake_logger) { instance_double(Steno::Logger, info: nil, warn: nil, debug: nil, error: nil) }
218282

219-
it 'does not create a TASK_STOPPED event, since no consumer ever saw the task start' do
220-
task.destroy
283+
before do
284+
allow(Steno).to receive(:logger).and_return(fake_logger)
285+
end
221286

222-
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
223-
expect(event).to be_nil
287+
context 'when the task is PENDING' do
288+
let(:task) { create(:task_model, app: parent_app, state: TaskModel::PENDING_STATE) }
289+
290+
it 'does not create a TASK_STOPPED event, since no consumer ever saw the task start' do
291+
task.destroy
292+
293+
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
294+
expect(event).to be_nil
295+
end
296+
297+
it 'does not log a warning, because a task that never ran is not supposed to have a start record' do
298+
task.destroy
299+
300+
expect(fake_logger).not_to have_received(:warn)
301+
end
302+
end
303+
304+
context 'when the task is RUNNING' do
305+
let(:task) { create(:task_model, app: parent_app, state: TaskModel::RUNNING_STATE) }
306+
307+
it 'does not create a TASK_STOPPED event, since no consumer ever saw the task start' do
308+
task.destroy
309+
310+
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
311+
expect(event).to be_nil
312+
end
313+
314+
it 'logs a warning, because a task that reached RUNNING must have had a start record once' do
315+
task.destroy
316+
317+
expect(fake_logger).to have_received(:warn).with(
318+
"Not writing a TASK_STOPPED usage event for task #{task.guid}: the task was running but has no " \
319+
'TASK_STARTED or TASK_WAS_RUNNING usage event on record. This should not happen. Run ' \
320+
"'rake db:was_running_backfill' and check whether usage events were deleted or purged."
321+
)
322+
end
224323
end
225324
end
226325

0 commit comments

Comments
 (0)