diff --git a/coderd/activitybump.go b/coderd/activitybump.go index 1324e9c821..059655ed8f 100644 --- a/coderd/activitybump.go +++ b/coderd/activitybump.go @@ -43,13 +43,29 @@ func activityBumpWorkspace(log slog.Logger, db database.Store, workspaceID uuid. return nil } - // We sent bumpThreshold slightly under bumpAmount to minimize DB writes. - const ( - bumpAmount = time.Hour - bumpThreshold = time.Hour - (time.Minute * 10) + workspace, err := s.GetWorkspaceByID(ctx, workspaceID) + if err != nil { + return xerrors.Errorf("get workspace: %w", err) + } + + var ( + // We bump by the original TTL to prevent counter-intuitive behavior + // as the TTL wraps. For example, if I set the TTL to 12 hours, sign off + // work at midnight, come back at 10am, I would want another full day + // of uptime. In the prior implementation, the workspace would enter + // a state of always expiring 1 hour in the future + bumpAmount = time.Duration(workspace.Ttl.Int64) + // DB writes are expensive so we only bump when 5% of the deadline + // has elapsed. + bumpEvery = bumpAmount / 20 + timeSinceLastBump = bumpAmount - time.Until(build.Deadline) ) - if !build.Deadline.Before(time.Now().Add(bumpThreshold)) { + if timeSinceLastBump < bumpEvery { + return nil + } + + if bumpAmount == 0 { return nil } diff --git a/coderd/activitybump_test.go b/coderd/activitybump_test.go index ffa5e434cf..7087ae516e 100644 --- a/coderd/activitybump_test.go +++ b/coderd/activitybump_test.go @@ -19,14 +19,17 @@ func TestWorkspaceActivityBump(t *testing.T) { ctx := context.Background() + const ttl = time.Minute + setupActivityTest := func(t *testing.T) (client *codersdk.Client, workspace codersdk.Workspace, assertBumped func(want bool)) { - var ttlMillis int64 = 60 * 1000 + ttlMillis := int64(ttl / time.Millisecond) client = coderdtest.New(t, &coderdtest.Options{ - AppHostname: proxyTestSubdomainRaw, - IncludeProvisionerDaemon: true, - AgentStatsRefreshInterval: time.Millisecond * 100, - MetricsCacheRefreshInterval: time.Millisecond * 100, + AppHostname: proxyTestSubdomainRaw, + IncludeProvisionerDaemon: true, + // Agent stats trigger the activity bump, so we want to report + // very frequently in tests. + AgentStatsRefreshInterval: time.Millisecond * 100, }) user := coderdtest.CreateFirstUser(t, client) @@ -67,11 +70,11 @@ func TestWorkspaceActivityBump(t *testing.T) { require.NoError(t, err) return workspace.LatestBuild.Deadline.Time != firstDeadline }, - testutil.WaitShort, testutil.IntervalFast, + testutil.WaitLong, testutil.IntervalFast, "deadline %v never updated", firstDeadline, ) - require.WithinDuration(t, database.Now().Add(time.Hour), workspace.LatestBuild.Deadline.Time, 3*time.Second) + require.WithinDuration(t, database.Now().Add(ttl), workspace.LatestBuild.Deadline.Time, 3*time.Second) } } @@ -87,6 +90,8 @@ func TestWorkspaceActivityBump(t *testing.T) { require.NoError(t, err) defer conn.Close() + // Must send network traffic after a few seconds to surpass bump threshold. + time.Sleep(time.Second * 3) sshConn, err := conn.SSHClient(ctx) require.NoError(t, err) _ = sshConn.Close() diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index d41d1b9035..c61320b912 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -62,7 +62,7 @@ export const Language = { ttlLabel: "Time until shutdown (hours)", ttlCausesShutdownHelperText: "Your workspace will shut down", ttlCausesShutdownAfterStart: - "after its next start. We delay shutdown by an hour whenever we detect activity", + "after its next start. We delay shutdown by this time whenever we detect activity", ttlCausesNoShutdownHelperText: "Your workspace will not automatically shut down.", formTitle: "Workspace schedule",