Skip to content

Commit 2e7cd3a

Browse files
authored
store: update how we model file changes (#6503)
before this PR, we kept a map of recent file changes, and deleted from the map when the changes were consumed. this creates race conditions if the events are processed in a different order than the changes, e.g., if a "consumed" event at 12:02 is processed before the file change event at 12:01. after this PR, we record both the file change time and the consumed time, then compare them to determine if the file change is still pending. Signed-off-by: Nick Santos <[email protected]>
1 parent d4bd91e commit 2e7cd3a

File tree

12 files changed

+175
-69
lines changed

12 files changed

+175
-69
lines changed

internal/controllers/core/tiltfile/reducers.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func HandleConfigsReloaded(
7373

7474
// Remove pending file changes that were consumed by this build.
7575
for _, status := range ms.BuildStatuses {
76-
status.ClearPendingChangesBefore(b.StartTime)
76+
status.ConsumeChangesBefore(b.StartTime)
7777
}
7878

7979
// Track the new secrets and go back to scrub them.

internal/engine/buildcontrol/build_control.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,15 @@ func waitingOnDependencies(state store.EngineState, mt *store.ManifestTarget) []
155155
// 3) Checks that the image still exists on the image store
156156
//
157157
// But in this particular context, we can cheat a bit.
158-
func canReuseImageTargetHeuristic(spec model.TargetSpec, status store.BuildStatus) bool {
158+
func canReuseImageTargetHeuristic(spec model.TargetSpec, status *store.BuildStatus) bool {
159159
id := spec.ID()
160160
if id.Type != model.TargetTypeImage {
161161
return false
162162
}
163163

164164
// NOTE(nick): A more accurate check might see if the pending file changes
165165
// are potentially live-updatable, but this is OK for the case of a base image.
166-
if len(status.PendingFileChanges) > 0 || len(status.PendingDependencyChanges) > 0 {
166+
if status.HasPendingFileChanges() || status.HasPendingDependencyChanges() {
167167
return false
168168
}
169169

@@ -505,14 +505,14 @@ func IsLiveUpdateTargetWaitingOnDeploy(state store.EngineState, mt *store.Manife
505505

506506
// Go through all the files, and make sure they're live-update-able.
507507
for id, status := range mt.State.BuildStatuses {
508-
if len(status.PendingFileChanges) == 0 {
508+
if !status.HasPendingFileChanges() {
509509
continue
510510
}
511511

512512
// We have an image target with changes!
513513
// First, make sure that all the changes match a sync.
514-
files := make([]string, 0, len(status.PendingFileChanges))
515-
for f := range status.PendingFileChanges {
514+
files := []string{}
515+
for f := range status.PendingFileChanges() {
516516
files = append(files, f)
517517
}
518518

@@ -605,7 +605,7 @@ func HoldLiveUpdateTargetsHandledByReconciler(state store.EngineState, mts []*st
605605
// Changes to the deploy target can't be live-updated.
606606
if mt.Manifest.DeployTarget != nil {
607607
bs, hasBuildStatus := mt.State.BuildStatuses[mt.Manifest.DeployTarget.ID()]
608-
hasPendingChanges := hasBuildStatus && len(bs.PendingFileChanges) > 0
608+
hasPendingChanges := hasBuildStatus && bs.HasPendingFileChanges()
609609
if hasPendingChanges {
610610
continue
611611
}
@@ -615,7 +615,7 @@ func HoldLiveUpdateTargetsHandledByReconciler(state store.EngineState, mts []*st
615615
iTargets := mt.Manifest.ImageTargets
616616
for _, iTarget := range iTargets {
617617
bs, hasBuildStatus := mt.State.BuildStatuses[iTarget.ID()]
618-
hasPendingChanges := hasBuildStatus && len(bs.PendingFileChanges) > 0
618+
hasPendingChanges := hasBuildStatus && bs.HasPendingFileChanges()
619619
if !hasPendingChanges {
620620
continue
621621
}

internal/engine/buildcontrol/build_control_test.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ func TestLiveUpdateMainImageHold(t *testing.T) {
322322
}
323323
f.st.KubernetesResources["sancho"] = resource
324324

325-
sancho.State.MutableBuildStatus(sanchoImage.ID()).PendingFileChanges[srcFile] = time.Now()
325+
sancho.State.MutableBuildStatus(sanchoImage.ID()).FileChanges[srcFile] = time.Now()
326326
f.assertNoTargetNextToBuild()
327327
f.assertHold("sancho", store.HoldReasonReconciling)
328328

@@ -338,7 +338,7 @@ func TestLiveUpdateMainImageHold(t *testing.T) {
338338
f.assertNoTargetNextToBuild()
339339

340340
// If the base image has a change, we have to rebuild.
341-
sancho.State.MutableBuildStatus(baseImage.ID()).PendingFileChanges[srcFile] = time.Now()
341+
sancho.State.MutableBuildStatus(baseImage.ID()).FileChanges[srcFile] = time.Now()
342342
f.assertNextTargetToBuild("sancho")
343343
}
344344

@@ -385,7 +385,7 @@ func TestLiveUpdateBaseImageHold(t *testing.T) {
385385
}
386386
f.st.KubernetesResources["sancho"] = resource
387387

388-
sancho.State.MutableBuildStatus(baseImage.ID()).PendingFileChanges[srcFile] = time.Now()
388+
sancho.State.MutableBuildStatus(baseImage.ID()).FileChanges[srcFile] = time.Now()
389389
f.assertNoTargetNextToBuild()
390390
f.assertHold("sancho", store.HoldReasonReconciling)
391391

@@ -401,7 +401,7 @@ func TestLiveUpdateBaseImageHold(t *testing.T) {
401401
f.assertNoTargetNextToBuild()
402402

403403
// If the deploy image has a change, we have to rebuild.
404-
sancho.State.MutableBuildStatus(sanchoImage.ID()).PendingFileChanges[srcFile] = time.Now()
404+
sancho.State.MutableBuildStatus(sanchoImage.ID()).FileChanges[srcFile] = time.Now()
405405
f.assertNextTargetToBuild("sancho")
406406
}
407407

@@ -472,15 +472,15 @@ func TestHoldForDeploy(t *testing.T) {
472472

473473
status := sancho.State.MutableBuildStatus(sanchoImage.ID())
474474

475-
status.PendingFileChanges[objFile] = time.Now()
475+
status.FileChanges[objFile] = time.Now().Add(-2 * time.Second)
476476
f.assertNextTargetToBuild("sancho")
477-
delete(status.PendingFileChanges, objFile)
477+
status.ConsumedChanges = time.Now().Add(-2 * time.Second)
478478

479-
status.PendingFileChanges[fallbackFile] = time.Now()
479+
status.FileChanges[fallbackFile] = time.Now().Add(-time.Second)
480480
f.assertNextTargetToBuild("sancho")
481-
delete(status.PendingFileChanges, fallbackFile)
481+
status.ConsumedChanges = time.Now().Add(-time.Second)
482482

483-
status.PendingFileChanges[srcFile] = time.Now()
483+
status.FileChanges[srcFile] = time.Now()
484484
f.assertNoTargetNextToBuild()
485485
f.assertHold("sancho", store.HoldReasonWaitingForDeploy)
486486

@@ -544,11 +544,11 @@ func TestHoldForManualLiveUpdate(t *testing.T) {
544544
// This shouldn't trigger a full-build, because it will be handled by the live-updater.
545545
status := sancho.State.MutableBuildStatus(sanchoImage.ID())
546546
f.st.AppendToTriggerQueue(sancho.Manifest.Name, model.BuildReasonFlagTriggerCLI)
547-
status.PendingFileChanges[srcFile] = time.Now()
547+
status.FileChanges[srcFile] = time.Now()
548548
f.assertNoTargetNextToBuild()
549549

550550
// This should trigger a full-rebuild, because we have a trigger without pending changes.
551-
delete(status.PendingFileChanges, srcFile)
551+
status.ConsumedChanges = time.Now()
552552
f.assertNextTargetToBuild("sancho")
553553
}
554554

internal/engine/buildcontroller.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,13 @@ func buildStateSet(ctx context.Context, manifest model.Manifest,
218218
id := spec.ID()
219219
status := ms.BuildStatus(id)
220220
var filesChanged []string
221-
for file := range status.PendingFileChanges {
221+
for file := range status.PendingFileChanges() {
222222
filesChanged = append(filesChanged, file)
223223
}
224224
sort.Strings(filesChanged)
225225

226226
var depsChanged []model.TargetID
227-
for dep := range status.PendingDependencyChanges {
227+
for dep := range status.PendingDependencyChanges() {
228228
depsChanged = append(depsChanged, dep)
229229
}
230230

internal/engine/buildcontroller_test.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func TestTriggerModes(t *testing.T) {
113113

114114
f.fsWatcher.Events <- watch.NewFileEvent(f.JoinPath("main.go"))
115115
f.WaitUntil("pending change appears", func(st store.EngineState) bool {
116-
return len(st.BuildStatus(manifest.ImageTargetAt(0).ID()).PendingFileChanges) >= 1
116+
return st.BuildStatus(manifest.ImageTargetAt(0).ID()).CountPendingFileChanges() >= 1
117117
})
118118

119119
if !tc.expectBuildWhenFilesChange {
@@ -161,7 +161,7 @@ func TestBuildControllerImageBuildTrigger(t *testing.T) {
161161
f.fsWatcher.Events <- watch.NewFileEvent(f.JoinPath("main.go"))
162162
}
163163
f.WaitUntil("pending change appears", func(st store.EngineState) bool {
164-
return len(st.BuildStatus(manifest.ImageTargetAt(0).ID()).PendingFileChanges) >= len(expectedFiles)
164+
return st.BuildStatus(manifest.ImageTargetAt(0).ID()).CountPendingFileChanges() >= len(expectedFiles)
165165
})
166166

167167
if manifest.TriggerMode.AutoOnChange() {
@@ -214,10 +214,10 @@ func TestBuildQueueOrdering(t *testing.T) {
214214

215215
f.fsWatcher.Events <- watch.NewFileEvent(f.JoinPath("main.go"))
216216
f.WaitUntil("pending change appears", func(st store.EngineState) bool {
217-
return len(st.BuildStatus(m1.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
218-
len(st.BuildStatus(m2.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
219-
len(st.BuildStatus(m3.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
220-
len(st.BuildStatus(m4.ImageTargetAt(0).ID()).PendingFileChanges) > 0
217+
return st.BuildStatus(m1.ImageTargetAt(0).ID()).HasPendingFileChanges() &&
218+
st.BuildStatus(m2.ImageTargetAt(0).ID()).HasPendingFileChanges() &&
219+
st.BuildStatus(m3.ImageTargetAt(0).ID()).HasPendingFileChanges() &&
220+
st.BuildStatus(m4.ImageTargetAt(0).ID()).HasPendingFileChanges()
221221
})
222222
f.assertNoCall("even tho there are pending changes, manual manifest shouldn't build w/o explicit trigger")
223223

@@ -270,10 +270,10 @@ func TestBuildQueueAndAutobuildOrdering(t *testing.T) {
270270

271271
f.fsWatcher.Events <- watch.NewFileEvent(f.JoinPath("dirManual/main.go"))
272272
f.WaitUntil("pending change appears", func(st store.EngineState) bool {
273-
return len(st.BuildStatus(m1.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
274-
len(st.BuildStatus(m2.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
275-
len(st.BuildStatus(m3.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
276-
len(st.BuildStatus(m4.ImageTargetAt(0).ID()).PendingFileChanges) > 0
273+
return st.BuildStatus(m1.ImageTargetAt(0).ID()).HasPendingFileChanges() &&
274+
st.BuildStatus(m2.ImageTargetAt(0).ID()).HasPendingFileChanges() &&
275+
st.BuildStatus(m3.ImageTargetAt(0).ID()).HasPendingFileChanges() &&
276+
st.BuildStatus(m4.ImageTargetAt(0).ID()).HasPendingFileChanges()
277277
})
278278
f.assertNoCall("even tho there are pending changes, manual manifest shouldn't build w/o explicit trigger")
279279

internal/engine/upper_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ k8s_yaml('snack.yaml')`
10931093

10941094
f.fsWatcher.Events <- watch.NewFileEvent(f.JoinPath("src/main.go"))
10951095
f.WaitUntil("pending change appears", func(st store.EngineState) bool {
1096-
return len(st.BuildStatus(imageTargetID).PendingFileChanges) > 0
1096+
return st.BuildStatus(imageTargetID).HasPendingFileChanges()
10971097
})
10981098
f.assertNoCall("even tho there are pending changes, manual manifest shouldn't build w/o explicit trigger")
10991099

internal/hud/view.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func StateToTerminalView(s store.EngineState, mu *sync.RWMutex) view.View {
5151

5252
var pendingBuildEdits []string
5353
for _, status := range ms.BuildStatuses {
54-
for f := range status.PendingFileChanges {
54+
for f := range status.PendingFileChanges() {
5555
pendingBuildEdits = append(pendingBuildEdits, f)
5656
}
5757
}

internal/hud/view_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestStateToViewRelativeEditPaths(t *testing.T) {
4747
},
4848
},
4949
}
50-
ms.MutableBuildStatus(m.ImageTargets[0].ID()).PendingFileChanges =
50+
ms.MutableBuildStatus(m.ImageTargets[0].ID()).FileChanges =
5151
map[string]time.Time{
5252
f.JoinPath("a", "b", "c", "foo"): time.Now(),
5353
f.JoinPath("a", "b", "c", "d", "e"): time.Now(),

internal/hud/webview/convert_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func TestStateToWebViewRelativeEditPaths(t *testing.T) {
5858
ms.BuildHistory = []model.BuildRecord{
5959
{},
6060
}
61-
ms.MutableBuildStatus(m.ImageTargets[0].ID()).PendingFileChanges =
61+
ms.MutableBuildStatus(m.ImageTargets[0].ID()).FileChanges =
6262
map[string]time.Time{
6363
f.JoinPath("a", "b", "c", "foo"): time.Now(),
6464
f.JoinPath("a", "b", "c", "d", "e"): time.Now(),

internal/store/buildcontrols/reducers.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func handleBuildResults(engineState *store.EngineState,
8181

8282
// Remove pending file changes that were consumed by this build.
8383
for _, status := range ms.BuildStatuses {
84-
status.ClearPendingChangesBefore(br.StartTime)
84+
status.ConsumeChangesBefore(br.StartTime)
8585
}
8686

8787
if isBuildSuccess {
@@ -124,7 +124,7 @@ func handleBuildResults(engineState *store.EngineState,
124124

125125
currentStatus := currentMS.MutableBuildStatus(id)
126126
currentStatus.LastResult = result
127-
currentStatus.ClearPendingChangesBefore(br.StartTime)
127+
currentStatus.ConsumeChangesBefore(br.StartTime)
128128
updatedIDSet[id] = true
129129
}
130130

@@ -155,7 +155,7 @@ func handleBuildResults(engineState *store.EngineState,
155155
}
156156

157157
// Otherwise, we need to mark it for rebuild to pick up the new image.
158-
currentMS.MutableBuildStatus(rDepID).PendingDependencyChanges[updatedID] = br.StartTime
158+
currentMS.MutableBuildStatus(rDepID).DependencyChanges[updatedID] = br.StartTime
159159
}
160160
}
161161
}

0 commit comments

Comments
 (0)