mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add prebuild invalidation via last_invalidated_at timestamp (#20582)
Updates #17917
This commit is contained in:
@@ -125,20 +125,29 @@ func (s GlobalSnapshot) IsHardLimited(presetID uuid.UUID) bool {
|
||||
}
|
||||
|
||||
// filterExpiredWorkspaces splits running workspaces into expired and non-expired
|
||||
// based on the preset's TTL.
|
||||
// If TTL is missing or zero, all workspaces are considered non-expired.
|
||||
// based on the preset's TTL and last_invalidated_at timestamp.
|
||||
// A prebuild is considered expired if:
|
||||
// 1. The preset has been invalidated (last_invalidated_at is set), OR
|
||||
// 2. It exceeds the preset's TTL (if TTL is set)
|
||||
// If TTL is missing or zero, only last_invalidated_at is checked.
|
||||
func filterExpiredWorkspaces(preset database.GetTemplatePresetsWithPrebuildsRow, runningWorkspaces []database.GetRunningPrebuiltWorkspacesRow) (nonExpired []database.GetRunningPrebuiltWorkspacesRow, expired []database.GetRunningPrebuiltWorkspacesRow) {
|
||||
if !preset.Ttl.Valid {
|
||||
return runningWorkspaces, expired
|
||||
}
|
||||
|
||||
ttl := time.Duration(preset.Ttl.Int32) * time.Second
|
||||
if ttl <= 0 {
|
||||
return runningWorkspaces, expired
|
||||
}
|
||||
|
||||
for _, prebuild := range runningWorkspaces {
|
||||
if time.Since(prebuild.CreatedAt) > ttl {
|
||||
isExpired := false
|
||||
|
||||
// Check if prebuild was created before last invalidation
|
||||
if preset.LastInvalidatedAt.Valid && prebuild.CreatedAt.Before(preset.LastInvalidatedAt.Time) {
|
||||
isExpired = true
|
||||
}
|
||||
|
||||
// Check TTL expiration if set
|
||||
if !isExpired && preset.Ttl.Valid {
|
||||
ttl := time.Duration(preset.Ttl.Int32) * time.Second
|
||||
if ttl > 0 && time.Since(prebuild.CreatedAt) > ttl {
|
||||
isExpired = true
|
||||
}
|
||||
}
|
||||
|
||||
if isExpired {
|
||||
expired = append(expired, prebuild)
|
||||
} else {
|
||||
nonExpired = append(nonExpired, prebuild)
|
||||
|
||||
@@ -600,6 +600,9 @@ func TestExpiredPrebuilds(t *testing.T) {
|
||||
running int32
|
||||
desired int32
|
||||
expired int32
|
||||
|
||||
invalidated int32
|
||||
|
||||
checkFn func(runningPrebuilds []database.GetRunningPrebuiltWorkspacesRow, state prebuilds.ReconciliationState, actions []*prebuilds.ReconciliationActions)
|
||||
}{
|
||||
// With 2 running prebuilds, none of which are expired, and the desired count is met,
|
||||
@@ -708,6 +711,52 @@ func TestExpiredPrebuilds(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validateState(t, expectedState, state)
|
||||
validateActions(t, expectedActions, actions)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preset has been invalidated - both instances expired",
|
||||
running: 2,
|
||||
desired: 2,
|
||||
expired: 0,
|
||||
invalidated: 2,
|
||||
checkFn: func(runningPrebuilds []database.GetRunningPrebuiltWorkspacesRow, state prebuilds.ReconciliationState, actions []*prebuilds.ReconciliationActions) {
|
||||
expectedState := prebuilds.ReconciliationState{Actual: 2, Desired: 2, Expired: 2}
|
||||
expectedActions := []*prebuilds.ReconciliationActions{
|
||||
{
|
||||
ActionType: prebuilds.ActionTypeDelete,
|
||||
DeleteIDs: []uuid.UUID{runningPrebuilds[0].ID, runningPrebuilds[1].ID},
|
||||
},
|
||||
{
|
||||
ActionType: prebuilds.ActionTypeCreate,
|
||||
Create: 2,
|
||||
},
|
||||
}
|
||||
|
||||
validateState(t, expectedState, state)
|
||||
validateActions(t, expectedActions, actions)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preset has been invalidated, but one prebuild instance is newer",
|
||||
running: 2,
|
||||
desired: 2,
|
||||
expired: 0,
|
||||
invalidated: 1,
|
||||
checkFn: func(runningPrebuilds []database.GetRunningPrebuiltWorkspacesRow, state prebuilds.ReconciliationState, actions []*prebuilds.ReconciliationActions) {
|
||||
expectedState := prebuilds.ReconciliationState{Actual: 2, Desired: 2, Expired: 1}
|
||||
expectedActions := []*prebuilds.ReconciliationActions{
|
||||
{
|
||||
ActionType: prebuilds.ActionTypeDelete,
|
||||
DeleteIDs: []uuid.UUID{runningPrebuilds[0].ID},
|
||||
},
|
||||
{
|
||||
ActionType: prebuilds.ActionTypeCreate,
|
||||
Create: 1,
|
||||
},
|
||||
}
|
||||
|
||||
validateState(t, expectedState, state)
|
||||
validateActions(t, expectedActions, actions)
|
||||
},
|
||||
@@ -719,7 +768,17 @@ func TestExpiredPrebuilds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// GIVEN: a preset.
|
||||
defaultPreset := preset(true, tc.desired, current)
|
||||
now := time.Now()
|
||||
invalidatedAt := now.Add(1 * time.Minute)
|
||||
|
||||
var muts []func(row database.GetTemplatePresetsWithPrebuildsRow) database.GetTemplatePresetsWithPrebuildsRow
|
||||
if tc.invalidated > 0 {
|
||||
muts = append(muts, func(row database.GetTemplatePresetsWithPrebuildsRow) database.GetTemplatePresetsWithPrebuildsRow {
|
||||
row.LastInvalidatedAt = sql.NullTime{Valid: true, Time: invalidatedAt}
|
||||
return row
|
||||
})
|
||||
}
|
||||
defaultPreset := preset(true, tc.desired, current, muts...)
|
||||
presets := []database.GetTemplatePresetsWithPrebuildsRow{
|
||||
defaultPreset,
|
||||
}
|
||||
@@ -727,11 +786,22 @@ func TestExpiredPrebuilds(t *testing.T) {
|
||||
// GIVEN: running prebuilt workspaces for the preset.
|
||||
running := make([]database.GetRunningPrebuiltWorkspacesRow, 0, tc.running)
|
||||
expiredCount := 0
|
||||
invalidatedCount := 0
|
||||
ttlDuration := time.Duration(defaultPreset.Ttl.Int32)
|
||||
for range tc.running {
|
||||
name, err := prebuilds.GenerateName()
|
||||
require.NoError(t, err)
|
||||
|
||||
prebuildCreateAt := time.Now()
|
||||
if int(tc.invalidated) > invalidatedCount {
|
||||
prebuildCreateAt = prebuildCreateAt.Add(-ttlDuration - 10*time.Second)
|
||||
invalidatedCount++
|
||||
} else if invalidatedCount > 0 {
|
||||
// Only `tc.invalidated` instances have been invalidated,
|
||||
// so the next instance is assumed to be created after `invalidatedAt`.
|
||||
prebuildCreateAt = invalidatedAt.Add(1 * time.Minute)
|
||||
}
|
||||
|
||||
if int(tc.expired) > expiredCount {
|
||||
// Update the prebuild workspace createdAt to exceed its TTL (5 seconds)
|
||||
prebuildCreateAt = prebuildCreateAt.Add(-ttlDuration - 10*time.Second)
|
||||
|
||||
Reference in New Issue
Block a user