Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions coderd/activitybump.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
19 changes: 12 additions & 7 deletions coderd/activitybump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down