Skip to content

Commit 0790a05

Browse files
committed
Emit TASK_STOPPED usage events based on recorded start evidence
create_stop_event_if_needed skipped the TASK_STOPPED event whenever the TASK_STARTED event was absent -- so a task whose start event had been pruned never got a stop event on completion, and a billing consumer that recorded the start would bill the task forever. Emit the stop when either piece of recorded start evidence exists: the TASK_STARTED event, or the TASK_WAS_RUNNING baseline seeded by the backfill for tasks that were already running when the keep-running cleanup was introduced. Between the two, a legitimately started task always has one -- the cleanup no longer prunes the start event of a running task, and the backfill covers tasks that had already lost theirs. When neither exists (e.g. a task canceled before it ever ran), no consumer ever saw the task start, so a stop event would be unmatched noise.
1 parent 65b257e commit 0790a05

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

app/models/runtime/task_model.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,16 @@ def create_start_event
137137
def create_stop_event_if_needed
138138
app_usage_repo = Repositories::AppUsageEventRepository.new
139139

140-
start_event = app_usage_repo.find_by_task_and_state(task: self, state: 'TASK_STARTED')
141-
existing_stop_event = app_usage_repo.find_by_task_and_state(task: self, state: 'TASK_STOPPED')
142-
return if start_event.nil? || existing_stop_event.present?
140+
return if app_usage_repo.find_by_task_and_state(task: self, state: 'TASK_STOPPED').present?
141+
142+
# Record the stop only when there is recorded evidence that the task
143+
# started: the TASK_STARTED event, or the TASK_WAS_RUNNING baseline seeded
144+
# for tasks that were already running when the keep-running cleanup was
145+
# introduced. Without either, no consumer ever saw the task start, so a
146+
# stop event would be unmatched noise.
147+
started = app_usage_repo.find_by_task_and_state(task: self, state: 'TASK_STARTED') ||
148+
app_usage_repo.find_by_task_and_state(task: self, state: 'TASK_WAS_RUNNING')
149+
return if started.nil?
143150

144151
create_stop_event
145152
end

spec/unit/models/runtime/task_model_spec.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,30 @@ module VCAP::CloudController
2828
expect(event.task_guid).to eq(task.guid)
2929
expect(event.parent_app_guid).to eq(task.app.guid)
3030
end
31+
32+
context 'when the TASK_STARTED event has been pruned and a TASK_WAS_RUNNING baseline exists' do
33+
let!(:start_event) { AppUsageEvent.make(task_guid: task.guid, state: 'TASK_WAS_RUNNING') }
34+
35+
it 'still creates a TASK_STOPPED event' do
36+
task.update(state: TaskModel::SUCCEEDED_STATE)
37+
38+
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
39+
expect(event).not_to be_nil
40+
expect(event.task_guid).to eq(task.guid)
41+
expect(event.parent_app_guid).to eq(task.app.guid)
42+
end
43+
end
44+
45+
context 'when there is neither a TASK_STARTED event nor a TASK_WAS_RUNNING baseline' do
46+
let!(:start_event) { nil }
47+
48+
it 'does not create a TASK_STOPPED event no consumer could match to a start' do
49+
task.update(state: TaskModel::SUCCEEDED_STATE)
50+
51+
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
52+
expect(event).to be_nil
53+
end
54+
end
3155
end
3256

3357
context 'when the task is moving to the FAILED_STATE' do
@@ -43,6 +67,28 @@ module VCAP::CloudController
4367
expect(event.task_guid).to eq(task.guid)
4468
expect(event.parent_app_guid).to eq(task.app.guid)
4569
end
70+
71+
context 'when the TASK_STARTED event has been pruned and a TASK_WAS_RUNNING baseline exists' do
72+
let!(:start_event) { AppUsageEvent.make(task_guid: task.guid, state: 'TASK_WAS_RUNNING') }
73+
74+
it 'still creates a TASK_STOPPED event' do
75+
task.update(state: TaskModel::FAILED_STATE)
76+
77+
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
78+
expect(event).not_to be_nil
79+
end
80+
end
81+
82+
context 'when there is neither a TASK_STARTED event nor a TASK_WAS_RUNNING baseline' do
83+
let!(:start_event) { nil }
84+
85+
it 'does not create a TASK_STOPPED event no consumer could match to a start' do
86+
task.update(state: TaskModel::FAILED_STATE)
87+
88+
event = AppUsageEvent.find(task_guid: task.guid, state: 'TASK_STOPPED')
89+
expect(event).to be_nil
90+
end
91+
end
4692
end
4793

4894
context 'when the task is moving from the PENDING state' do

0 commit comments

Comments
 (0)